モノレポによるフルスタックTypeScript開発の効率化
Ethan Miller
Product Engineer · Leapcell

はじめに
現代のウェブ開発の活気ある状況において、堅牢なフルスタックアプリケーションの構築は、フロントエンドフレームワークとバックエンドサービス間の複雑な相互作用を伴うことがよくあります。プロジェクトがスケールするにつれて、ReactアプリケーションとNestJS APIの別々のコードベースを管理することは、すぐに大きなオーバーヘッドになる可能性があります。断片化は、構成の重複、一貫性のない依存関係、非効率的なビルドプロセスにつながります。ここでモノレポアプローチが輝きを放ち、これらの課題に正面から取り組む統一された開発体験を提供します。関連プロジェクトを単一のリポジトリに統合することで、コード共有、ビルド最適化、全体的な開発者の生産性において強力な利点を解き放つことができます。この記事では、NxやTurborepoのようなツールが、フルスタック(React + NestJS)TypeScriptプロジェクトでこの合理化されたワークフローをどのように具体的に実現するかを掘り下げます。
モノレポの利点を理解する
NxとTurborepoの具体例に入る前に、モノレポのコンテキストでそれらの力を支えるコアコンセプトの基本的な理解を確立しましょう。
モノレポ: 複数の個別のプロジェクト(例:フロントエンド、バックエンド、共有ライブラリ)が1つのバージョン管理リポジトリに格納される開発戦略。これは、各プロジェクトが独自の別々のリポジトリに配置される「ポリレポ」アプローチとは対照的です。
ワークスペース: NxとTurborepoのコンテキストでは、ワークスペースはモノレポのルートディレクトリを指し、すべての個々のアプリケーションとライブラリを含みます。
アプリケーション (App): モノレポ内のデプロイ可能なユニット。たとえば、ReactフロントエンドやNestJSバックエンドAPIなどです。
ライブラリ (Lib): モノレポ内の複数のアプリケーションや他のライブラリ間で共有できるコードの再利用可能なブロック。これは、一貫性を強制し、冗長性を削減するための基盤です。例としては、共有UIコンポーネント、データモデル、ユーティリティ関数、APIインターフェースなどがあります。
タスクランナー / ビルドシステム: NxやTurborepoのようなツールは、インテリジェントなタスクランナーとして機能します。それらはモノレポ内のプロジェクト間の依存関係を理解し、ビルド、テスト、リンティング、サービングなどのさまざまな開発タスクを最適化できます。
依存関係グラフ: これらのツールがプロジェクト(アプリおよびライブラリ)間の関係を表す重要な概念。このグラフにより、変更後にどのプロジェクトを再構築する必要があるかを判断し、タスクを正しい順序で実行できます。
分散キャッシュ: 開発をさらに加速するために、これらのツールはキャッシュを活用します。依存関係が変更されていない場合、ビルド出力は(ローカルまたはリモートの)キャッシュから再利用でき、後続のビルドおよびテストの時間が大幅に短縮されます。
次に、NxとTurborepoがこれらの概念を実装して、フルスタックReact + NestJS TypeScriptプロジェクトを合理化する方法を見てみましょう。
NxによるフルスタックTypeScript開発
Nxは、モノレポ哲学を採用した強力で拡張可能なビルドシステムです。大規模でエンタープライズグレードのアプリケーションを管理するための包括的な機能セットを提供します。強力な意見とコード生成機能を提供し、プロジェクトの初期化と一貫性の維持に優れています。
Nxワークスペースの設定
新しいNxワークスペースを作成し、ReactおよびNestJSアプリケーションを追加することから始めましょう。
npx create-nx-workspace@latest fullstack-monorepo --preset=react-standalone --appName=frontend --style=css
このコマンドは、frontend
という名前のReactアプリケーションを持つfullstack-monorepo
という名前の新しいNxワークスペースを作成します。次に、NestJSアプリケーション(APIとも呼ばれます)を追加しましょう。
cd fullstack-monorepo npm install -D @nx/nest nx g @nx/nest:application backend --dryRun=false
これで、ワークスペースの構造は次のようになります。
fullstack-monorepo/
├── apps/
│ ├── backend/ # NestJSアプリケーション
│ └── frontend/ # Reactアプリケーション
├── libs/ # 共有ライブラリ
├── nx.json # Nx設定
├── package.json
└── tsconfig.base.json
ライブラリの作成と共有
モノレポの真の力は、コードの共有を開始するときに現れます。データモデルまたはAPI型用の共有ライブラリを作成しましょう。
nx g @nx/js:library shared-data --dryRun=false
これにより、libs/shared-data
が作成されます。libs/shared-data/src/lib/shared-data.ts
でインターフェースを定義できます。
// libs/shared-data/src/lib/shared-data.ts export interface User { id: string; name: string; email: string; } export function getUserGreeting(user: User): string { return `Hello, ${user.name}!`; }
これで、frontend
とbackend
の両方がこのライブラリを利用できるようになります。
apps/backend/src/app/app.service.ts
で:
// apps/backend/src/app/app.service.ts import { Injectable } from '@nestjs/common'; import { User, getUserGreeting } from '@fullstack-monorepo/shared-data'; // ライブラリからインポート @Injectable() export class AppService { getData(): { message: string } { const user: User = { id: '1', name: 'Alice', email: 'alice@example.com' }; return { message: getUserGreeting(user) }; } }
そしてapps/frontend/src/app/app.tsx
で:
// apps/frontend/src/app/app.tsx import React, { useEffect, useState } from 'react'; import { User, getUserGreeting } from '@fullstack-monorepo/shared-data'; // ライブラリからインポート export function App() { const [greeting, setGreeting] = useState(''); useEffect(() => { // フロントエンドでの使用例 const user: User = { id: '2', name: 'Bob', email: 'bob@example.com' }; setGreeting(getUserGreeting(user)); // 例:NestJSバックエンドからのデータ取得 fetch('/api') .then((res) => res.json()) .then((data) => console.log('Backend response:', data.message)); }, []); return ( <> <h1>Welcome, Frontend!</h1> <p>{greeting}</p> </> ); } export default App;
@fullstack-monorepo/shared-data
エイリアスを使用したインポートパスに注意してください。Nxは、シームレスなインポートのためにtsconfig.base.json
でこれらのTypeScriptパスマッピングを自動的に構成します。
Nxでのタスクの実行
Nxは、モノレポ全体でタスクを実行するための強力なコマンドを提供します。
- 両方のアプリケーションを並行して実行:
nx run-many --target=serve --projects=frontend,backend --parallel
- 影響を受けるすべてのプロジェクトをビルド:
このコマンドはインテリジェントに変更を検出し、直接的または間接的に影響を受けるプロジェクトのみを再構築します。nx affected:build
- テストの実行:
nx test backend
Nxの依存関係グラフ分析は、タスクが効率的に実行されることを保証し、そのキャッシュメカニズム(ローカルおよびリモート)はビルド時間を劇的に短縮します。
スピードとシンプルさのためのTurborepo
Vercelに買収されたTurborepoは、スピードと使いやすさに焦点を当てている点で差別化されています。それは本質的に、JavaScriptおよびTypeScriptモノレポのための高性能ビルドシステムであり、高速ビルド、スマートキャッシュ、効率的なタスクオーケストレーションを優先しています。Nxはより意見のある、フルスイートの体験とジェネレーターとプラグインを提供しますが、Turborepoはよりニュートラルであり、純粋に高速化されたビルドに焦点を当てています。
Turborepoワークスペースの設定
Turborepoをデモンストレーションするために、新たに始めましょう。
npx create-turbo-app
プロンプトに従い、今回はminimal
セットアップを選択します。これにより、基本的なワークスペース構造が作成されます。
turbo-monorepo/
├── apps/
├── packages/ # Nxのlibsに似ています
├── turborepo.json
├── package.json
ReactおよびNestJSアプリケーションを手動で作成します。
Reactの場合:
apps/frontend
で、標準的なReactプロジェクト(例:ViteまたはCreate React Appを使用)を作成します。
frontend
のpackage.json
:
{ "name": "frontend", "version": "1.0.0", "private": true, "scripts": { "dev": "vite", "build": "vite build", "serve": "vite preview" }, "dependencies": { "react": "^18.2.0", "react-dom": "^18.2.0" }, "devDependencies": { "@types/react": "^18.2.15", "@types/react-dom": "^18.2.7", "@vitejs/plugin-react": "^4.0.3", "typescript": "^5.0.2", "vite": "^4.4.5" } }
NestJSの場合:
apps/backend
で、NestJSプロジェクトを作成します。
backend
のpackage.json
:
{ "name": "backend", "version": "1.0.0", "private": true, "scripts": { "build": "nest build", "start": "nest start", "start:dev": "nest start --watch" }, "dependencies": { "@nestjs/common": "^10.0.0", "@nestjs/core": "^10.0.0", "@nestjs/platform-express": "^10.0.0", "reflect-metadata": "^0.1.13", "rxjs": "^7.8.1" }, "devDependencies": { "@nestjs/cli": "^10.0.0", "@nestjs/schematics": "^10.0.0", "@nestjs/testing": "^10.0.0", "@types/express": "^4.17.17", "@types/jest": "^29.5.2", "@types/node": "^20.3.1", "@types/supertest": "^2.0.12", "jest": "^29.5.0", "source-map-support": "^0.5.21", "supertest": "^6.3.3", "ts-jest": "^29.1.0", "ts-loader": "^9.4.3", "ts-node": "^10.9.1", "tsconfig-paths": "^4.2.0", "typescript": "^5.1.3" } }
共有パッケージ (ライブラリ)
Turborepoでは、共有コードはpackages
ディレクトリに配置されます。packages/shared-data
を作成しましょう。
packages/shared-data/package.json
:
{ "name": "shared-data", "version": "1.0.0", "main": "./src/index.ts", "types": "./src/index.ts", "private": true, "scripts": { "build": "echo \"No build needed for shared-data, direct TS usage.\"" } }
packages/shared-data/src/index.ts
:
// packages/shared-data/src/index.ts export interface User { id: string; name: string; email: string; } export function getUserGreeting(user: User): string { return `Hello, ${user.name}!`; }
これらのアプリケーションで使用するには、それぞれのpackage.json
ファイルにshared-data
を依存関係として追加します。
apps/backend/package.json
で:
{ // ... その他のフィールド "dependencies": { "@nestjs/common": "^10.0.0", // ... "shared-data": "workspace:*" // npm/yarn/pnpmにローカルパッケージにリンクするように指示します } }
apps/frontend/package.json
で:
{ // ... その他のフィールド "dependencies": { "react": "^18.2.0", "react-dom": "^18.2.0", "shared-data": "workspace:*" } }
その後、モノレポのルートでnpm install
を実行して、これらのパッケージをリンクします。
Turborepoタスクの設定
Turborepoの心臓部は、ワークスペースのルートにあるturbo.json
ファイルです。これは、各パッケージのタスク(build
、dev
、test
)とその依存関係を定義します。
turbo.json
:
{ "$schema": "https://turbo.build/schema.json", "pipe": { "build": { "cache": true, "dependsOn": ["^build"], "outputs": ["dist/**"] }, "dev": { "cache": false, "persistent": true }, "test": { "cache": true, "outputs": ["coverage/**"] } } }
cache: true
:build
およびtest
タスクのキャッシュを有効にします。- `dependsOn: [