Turborepo + pnpmで作る快適モノレポ開発環境
TurborepoとpnpmでAPIとフロントエンドを1リポジトリ管理するモノレポ構成の作り方。pnpm workspaceの設定、turbo.jsonの依存関係定義、型共有のメリットを実例で解説。
プロジェクトが大きくなってくると、「これ、どう整理したらいいんだろう」と悩むことがあります。
APIとフロントエンドを同じリポジトリで管理したいけど、依存関係がぐちゃぐちゃになるのは嫌だなと思っていました。そこで試してみたのがTurborepo + pnpmのモノレポ構成です。
なぜモノレポにしたか
正直、最初は「モノレポって大げさじゃない?」と思っていました。
でも実際に使ってみると、地味に便利なポイントがいくつかあって:
- APIとフロントエンドで型定義を共有できる
- どちらかを変更したときに、影響範囲がすぐわかる
pnpm dev一発で全部立ち上がる
特に型の共有は大きくて、APIのレスポンス型を変えたらフロントエンドでTypeScriptエラーが出る、という体験は一度味わうと戻れなくなります。
プロジェクト構成
こんな感じの構成にしています:
project/
├── apps/
│ ├── api/ # バックエンドAPI
│ ├── web/ # フロントエンドSPA
│ └── ... # その他のアプリ
├── packages/
│ ├── domain/ # エンティティ、値オブジェクト
│ ├── usecase/ # ビジネスロジック
│ ├── infrastructure/ # DB実装
│ └── ... # 共有ライブラリ
└── turbo.json
apps/ にはデプロイ単位のアプリケーション、packages/ には共有ライブラリを置いています。
pnpmのワークスペース設定
pnpm-workspace.yaml はシンプルです:
packages:
- apps/*
- apps/workers/*
- apps/batch/*
- packages/*
catalog:
'@hono/zod-openapi': 1.2.0
drizzle-orm: 0.45.1
hono: 4.11.1
zod: 4.2.1
catalog という機能が地味に便利で、複数パッケージで使うライブラリのバージョンを一箇所で管理できます。各 package.json では "zod": "catalog:" と書くだけ。バージョンアップ時に全パッケージを一括で変更しなくて済むのがいい。
Turborepoのタスク設定
turbo.json では依存関係を定義します:
{
"$schema": "https://turbo.build/schema.json",
"tasks": {
"build": {
"dependsOn": ["^build", "^generate"],
"outputs": ["dist/**"]
},
"dev": {
"cache": false,
"persistent": true,
"dependsOn": ["^build"]
},
"generate": {
"dependsOn": ["^generate:openapi"],
"outputs": ["src/generated/**"]
}
}
}
^build の ^ は「このパッケージが依存しているパッケージのbuildを先に実行する」という意味です。これがあるおかげで、pnpm build と打つだけで正しい順序でビルドが走ります。
依存関係の方向
パッケージ間の依存は一方向に制限しています:
domain ← usecase ← infrastructure/routes ← apps
domain は他のパッケージに依存しない純粋な層。エンティティや値オブジェクトを定義していて、Zodスキーマがそのまま型として使われます。
// packages/domain/src/entities/item.ts
import { z } from "zod";
export const Item = z.object({
id: ItemId,
name: ItemName,
status: ItemStatus,
});
export type Item = z.infer<typeof Item>;
この Item 型がAPIレスポンス、DBモデル、フロントエンドの状態管理まで一貫して使われる。型の変更が自動的に全層に伝播するので、「ここ変えたけど、他にも変えなきゃいけない場所あったっけ?」という心配がなくなります。
開発時のワークフロー
普段の開発はこんな感じ:
# 全サービス起動
pnpm dev
# 特定のパッケージだけ
pnpm --filter @myapp/api dev
Turborepoのキャッシュが効いているので、変更していないパッケージは再ビルドされません。packages/domain を変更したら依存しているパッケージだけ再ビルド、みたいな感じで動いてくれる。
型チェック・Lintの一括実行
CI/CDや手元での確認用に、全パッケージの品質チェックを一括で実行できます:
pnpm type-check # 全パッケージのtsc
pnpm lint # 全パッケージのBiome
pnpm test # 全パッケージのテスト
これもTurborepoの依存関係解決のおかげで、必要なビルドが先に走ってから型チェックが実行されます。
感想
モノレポにしてみて思うのは、「意外とアリ」ということ。
最初はセットアップが面倒かな?と思っていたけど、Turborepo + pnpmの組み合わせはドキュメントがしっかりしていて、短時間で動く状態にできました。
特に型の共有とキャッシュの恩恵は大きくて、「APIの型変えたのにフロントの修正忘れてた」みたいなバグが消えるのは精神的に楽です。