Prisma を Monorepo で使い倒す:パス解決・generate の共有・依存戦略
Monorepo 環境で Prisma を活用する際、パス解決や generate の共有、依存関係の管理など、特有の課題に直面することがあります。複数のパッケージで同じデータベーススキーマを共有したい、あるいは各パッケージで異なる Prisma スキーマを管理したいといったニーズに対応するには、適切な戦略が必要です。
本記事では、Monorepo における Prisma の実践的な運用方法を、実際のコード例とともに詳しく解説していきます。効率的な開発環境を構築し、チーム全体の生産性を向上させましょう。
背景
Monorepo は、複数のプロジェクトやパッケージを単一のリポジトリで管理する手法として、近年多くの開発チームで採用されています。フロントエンド、バックエンド、共有ライブラリなどを一元管理することで、コードの再利用性が高まり、依存関係の管理も容易になります。
一方、Prisma は TypeScript/JavaScript のための次世代 ORM として、型安全なデータベースアクセスを提供しています。スキーマファイルからクライアントコードを自動生成する仕組みにより、開発体験が大幅に向上します。
Monorepo における Prisma の位置づけ
以下の図は、典型的な Monorepo 構成と Prisma の配置パターンを示しています。
mermaidflowchart TB
root["Monorepo ルート"]
packages["packages/"]
api["api<br/>(Backend)"]
web["web<br/>(Frontend)"]
shared["shared<br/>(共有ライブラリ)"]
db["database<br/>(Prisma)"]
root --> packages
packages --> api
packages --> web
packages --> shared
packages --> db
api -.->|依存| db
web -.->|依存| db
shared -.->|依存| db
style db fill:#e1f5ff
style api fill:#fff4e1
style web fill:#fff4e1
style shared fill:#f0f0f0
このような構成では、データベーススキーマを専用のパッケージとして切り出し、他のパッケージから参照する形が一般的です。しかし、この際にパス解決や generate コマンドの実行タイミング、依存関係の適切な設定が課題となります。
Monorepo ツールの選択肢
現在、主流となっている Monorepo 管理ツールには以下のようなものがあります。
| # | ツール名 | 特徴 | Prisma との相性 |
|---|---|---|---|
| 1 | Yarn Workspaces | シンプルで軽量、npm/yarn と親和性が高い | ★★★ |
| 2 | Turborepo | キャッシュ機能が強力、ビルド高速化に優れる | ★★★ |
| 3 | Nx | 大規模プロジェクト向け、豊富なプラグイン | ★★☆ |
| 4 | Lerna | 歴史が長い、パッケージ公開に強い | ★★☆ |
本記事では、Yarn Workspaces をベースに解説を進めますが、他のツールでも基本的な考え方は応用できます。
課題
Monorepo で Prisma を利用する際、以下のような課題に直面することがあります。
パス解決の問題
Prisma Client を import する際、各パッケージから正しいパスで参照できるようにする必要があります。特に @prisma/client のインポートパスと、実際の Prisma スキーマの配置場所の不一致が問題になりやすいです。
generate コマンドの実行タイミング
prisma generate コマンドをいつ、どこで実行するかという問題があります。各パッケージで個別に実行するのか、ルートで一括実行するのか、あるいはビルド時に自動実行するのか、最適な方法を選択する必要があります。
依存関係の管理
複数のパッケージで Prisma を共有する場合、バージョンの統一や依存関係の明示的な宣言が重要です。特に @prisma/client と prisma CLI のバージョンが一致していないと、予期しないエラーが発生することがあります。
以下の図は、よくある問題のパターンを示しています。
mermaidflowchart LR
subgraph problem["❌ 問題のあるパターン"]
api1["api パッケージ"]
web1["web パッケージ"]
db1["database パッケージ"]
api1 -->|"バージョン 4.0.0"| db1
web1 -->|"バージョン 5.0.0"| db1
db1 -.->|"バージョン不一致<br/>でエラー"| err["generate 失敗"]
end
style err fill:#ffe1e1
style problem fill:#fff9f0
バージョンの不一致や、パスの誤った参照により、開発環境が不安定になってしまいます。
マイグレーション戦略
スキーマの変更を複数のパッケージに反映させる際、マイグレーションの実行順序やタイミングも考慮しなければなりません。開発環境と CI/CD 環境での挙動の違いも注意が必要です。
解決策
これらの課題に対する実践的な解決策を、段階的に見ていきましょう。
基本的なディレクトリ構成
まず、Monorepo の基本構成を整えます。以下のようなディレクトリ構造を推奨します。
gomonorepo-root/
├── package.json
├── yarn.lock
└── packages/
├── database/
│ ├── package.json
│ ├── prisma/
│ │ └── schema.prisma
│ └── src/
│ └── index.ts
├── api/
│ ├── package.json
│ └── src/
│ └── index.ts
└── web/
├── package.json
└── src/
└── index.ts
この構成により、Prisma スキーマを専用パッケージとして管理し、他のパッケージから参照できるようになります。
ルートの package.json 設定
Monorepo のルートディレクトリに配置する package.json を設定します。Yarn Workspaces を有効化し、共通のスクリプトを定義します。
json{
"name": "monorepo-root",
"version": "1.0.0",
"private": true,
"workspaces": ["packages/*"]
}
private: true を設定することで、ルートパッケージが誤って公開されるのを防ぎます。workspaces フィールドで、どのディレクトリをワークスペースとして扱うかを指定します。
次に、共通で使用する依存関係とスクリプトを追加します。
json{
"name": "monorepo-root",
"version": "1.0.0",
"private": true,
"workspaces": ["packages/*"],
"scripts": {
"db:generate": "yarn workspace @monorepo/database prisma generate",
"db:migrate": "yarn workspace @monorepo/database prisma migrate dev",
"db:studio": "yarn workspace @monorepo/database prisma studio"
},
"devDependencies": {
"prisma": "^5.8.0",
"typescript": "^5.3.0"
}
}
ルートレベルで Prisma CLI をインストールし、各パッケージからコマンドを実行できるようにします。yarn workspace コマンドを使うことで、特定のパッケージ内でコマンドを実行できます。
database パッケージの設定
Prisma スキーマを管理する専用パッケージを作成します。まず packages/database/package.json を設定しましょう。
json{
"name": "@monorepo/database",
"version": "1.0.0",
"main": "./src/index.ts",
"types": "./src/index.ts"
}
パッケージ名には @monorepo/ のようなスコープを付けることで、他の npm パッケージとの衝突を防ぎます。main と types フィールドで、エントリーポイントを明示的に指定します。
次に、Prisma 関連の依存関係を追加します。
json{
"name": "@monorepo/database",
"version": "1.0.0",
"main": "./src/index.ts",
"types": "./src/index.ts",
"scripts": {
"generate": "prisma generate",
"migrate": "prisma migrate dev",
"studio": "prisma studio"
},
"dependencies": {
"@prisma/client": "^5.8.0"
},
"devDependencies": {
"prisma": "^5.8.0"
}
}
@prisma/client を dependencies に、prisma CLI を devDependencies に配置します。バージョンは必ず一致させてください。
Prisma スキーマの設定
packages/database/prisma/schema.prisma ファイルを作成し、データベーススキーマを定義します。
prisma// データソースの設定
datasource db {
provider = "postgresql"
url = env("DATABASE_URL")
}
datasource ブロックでは、使用するデータベースの種類と接続情報を定義します。url には環境変数を使用することで、環境ごとに異なる接続先を設定できます。
次に、Prisma Client の生成設定を追加します。
prisma// Prisma Client の生成設定
generator client {
provider = "prisma-client-js"
output = "../node_modules/.prisma/client"
}
output フィールドが重要なポイントです。デフォルトでは node_modules/@prisma/client に生成されますが、Monorepo では明示的にパスを指定することで、他のパッケージからも確実に参照できるようにします。
データモデルの定義例を示します。
prisma// ユーザーモデルの定義
model User {
id Int @id @default(autoincrement())
email String @unique
name String?
posts Post[]
createdAt DateTime @default(now())
updatedAt DateTime @updatedAt
}
各フィールドには型とデコレーターを指定します。@id で主キー、@unique で一意制約、@default でデフォルト値を設定できます。
リレーションの定義も追加しましょう。
prisma// 投稿モデルの定義
model Post {
id Int @id @default(autoincrement())
title String
content String?
published Boolean @default(false)
author User @relation(fields: [authorId], references: [id])
authorId Int
createdAt DateTime @default(now())
updatedAt DateTime @updatedAt
}
@relation デコレーターで、User と Post の関係を定義しています。fields で外部キーフィールド、references で参照先のフィールドを指定します。
database パッケージのエクスポート設定
生成された Prisma Client を他のパッケージから使用できるようにするため、packages/database/src/index.ts を作成します。
typescript// Prisma Client のエクスポート
export { PrismaClient } from '@prisma/client';
シンプルに Prisma Client を再エクスポートすることで、他のパッケージから @monorepo/database を通じてアクセスできるようになります。
より便利にするため、シングルトンインスタンスも提供しましょう。
typescript// Prisma Client のシングルトンインスタンス
import { PrismaClient } from '@prisma/client';
// グローバルな型定義を拡張
declare global {
var prisma: PrismaClient | undefined;
}
TypeScript のグローバル型定義を拡張することで、開発モードでのホットリロード時にインスタンスを再利用できます。
インスタンスの作成と管理のロジックを実装します。
typescript// Prisma インスタンスの作成と管理
export const prisma =
global.prisma ||
new PrismaClient({
log: ['query', 'error', 'warn'],
});
// 開発環境ではグローバルに保存
if (process.env.NODE_ENV !== 'production') {
global.prisma = prisma;
}
開発環境では global.prisma にインスタンスを保存し、ホットリロード時に再利用します。本番環境では毎回新しいインスタンスを作成します。
型情報もエクスポートしておくと便利です。
typescript// Prisma の型をエクスポート
export type { User, Post } from '@prisma/client';
これにより、他のパッケージで型を使用する際に、直接 @prisma/client をインポートする必要がなくなります。
api パッケージの設定
バックエンド API パッケージから database パッケージを使用する設定を行います。まず packages/api/package.json を作成します。
json{
"name": "@monorepo/api",
"version": "1.0.0",
"main": "./src/index.ts",
"scripts": {
"dev": "tsx watch src/index.ts",
"build": "tsc"
}
}
基本的なパッケージ情報とスクリプトを定義します。tsx は TypeScript を直接実行できる便利なツールです。
database パッケージへの依存関係を追加します。
json{
"name": "@monorepo/api",
"version": "1.0.0",
"main": "./src/index.ts",
"scripts": {
"dev": "tsx watch src/index.ts",
"build": "tsc"
},
"dependencies": {
"@monorepo/database": "*"
},
"devDependencies": {
"tsx": "^4.7.0",
"typescript": "^5.3.0"
}
}
"@monorepo/database": "*" により、同じワークスペース内のパッケージを参照します。バージョンを * にすることで、常に最新のローカルパッケージを使用できます。
api パッケージでの Prisma 使用例
実際に API パッケージで Prisma を使用するコード例を示します。packages/api/src/index.ts を作成しましょう。
typescript// Prisma Client のインポート
import { prisma, User } from '@monorepo/database';
database パッケージから Prisma Client と型をインポートします。パスは @monorepo/database だけで、シンプルに記述できます。
ユーザーを作成する関数の実装例です。
typescript// ユーザー作成関数
async function createUser(
email: string,
name: string
): Promise<User> {
// Prisma Client を使ってユーザーを作成
const user = await prisma.user.create({
data: {
email,
name,
},
});
return user;
}
prisma.user.create() メソッドは完全に型安全です。TypeScript が自動的に補完とエラーチェックを行ってくれます。
ユーザー一覧を取得する関数も実装しましょう。
typescript// ユーザー一覧取得関数
async function getAllUsers(): Promise<User[]> {
// リレーションを含めてユーザーを取得
const users = await prisma.user.findMany({
include: {
posts: true, // 投稿も含める
},
});
return users;
}
include オプションで関連するデータも同時に取得できます。これもすべて型安全に記述できます。
メイン処理の実装例です。
typescript// メイン処理
async function main() {
try {
// ユーザーを作成
const user = await createUser(
'test@example.com',
'テストユーザー'
);
console.log('ユーザーを作成しました:', user);
// 全ユーザーを取得
const users = await getAllUsers();
console.log('全ユーザー:', users);
} catch (error) {
console.error('エラーが発生しました:', error);
throw error;
} finally {
// 接続を切断
await prisma.$disconnect();
}
}
// プログラム実行
main();
finally ブロックで prisma.$disconnect() を呼び出し、データベース接続を適切にクリーンアップします。
パス解決の最適化
TypeScript のパス解決をさらに最適化するため、tsconfig.json を設定します。packages/api/tsconfig.json を作成しましょう。
json{
"compilerOptions": {
"target": "ES2022",
"module": "commonjs",
"lib": ["ES2022"],
"outDir": "./dist",
"rootDir": "./src",
"strict": true,
"esModuleInterop": true,
"skipLibCheck": true,
"forceConsistentCasingInFileNames": true
}
}
基本的な TypeScript の設定を行います。strict: true で厳密な型チェックを有効にしています。
パスマッピングの設定を追加します。
json{
"compilerOptions": {
"target": "ES2022",
"module": "commonjs",
"lib": ["ES2022"],
"outDir": "./dist",
"rootDir": "./src",
"strict": true,
"esModuleInterop": true,
"skipLibCheck": true,
"forceConsistentCasingInFileNames": true,
"paths": {
"@monorepo/database": ["../database/src"]
}
}
}
paths オプションで、@monorepo/database を相対パスにマッピングします。これにより、IDE の補完やジャンプ機能が正しく動作するようになります。
generate コマンドの実行戦略
Prisma Client を生成するタイミングとして、以下の 3 つの戦略があります。
戦略 1: 手動実行
開発者が必要に応じて手動で実行する方法です。ルートディレクトリで以下のコマンドを実行します。
bash# database パッケージで generate を実行
yarn db:generate
シンプルですが、スキーマ変更後に実行を忘れるリスクがあります。小規模なチームや個人開発に適しています。
戦略 2: postinstall スクリプト
依存関係のインストール後に自動実行する方法です。packages/database/package.json に以下を追加します。
json{
"scripts": {
"postinstall": "prisma generate"
}
}
yarn install を実行すると、自動的に prisma generate が実行されます。CI/CD 環境でも確実に実行されるため、おすすめの方法です。
戦略 3: prepare スクリプト
Git フックと連携する方法です。ルートの package.json に以下を追加します。
json{
"scripts": {
"prepare": "yarn db:generate"
}
}
yarn install 後、git clone 後などに自動実行されます。ただし、実行頻度が高すぎる場合があるため、状況に応じて使い分けましょう。
以下の図は、各戦略の実行タイミングを示しています。
mermaidflowchart TB
start["開発開始"]
install["yarn install"]
schema["schema.prisma<br/>編集"]
subgraph strategy1["戦略1: 手動実行"]
manual["yarn db:generate<br/>(手動)"]
end
subgraph strategy2["戦略2: postinstall"]
auto1["prisma generate<br/>(自動)"]
end
subgraph strategy3["戦略3: prepare"]
auto2["prisma generate<br/>(自動)"]
end
start --> install
install --> strategy2
install --> strategy3
schema --> strategy1
style strategy1 fill:#fff4e1
style strategy2 fill:#e1f5ff
style strategy3 fill:#e8f5e1
開発フローに合わせて、最適な戦略を選択してください。
環境変数の管理
データベース接続情報などの環境変数を適切に管理することも重要です。ルートディレクトリに .env ファイルを作成します。
bash# データベース接続URL
DATABASE_URL="postgresql://user:password@localhost:5432/mydb"
本番環境とは異なる接続情報を使用します。このファイルは .gitignore に追加し、リポジトリにコミットしないようにしましょう。
.env.example ファイルも作成しておくと、他の開発者がセットアップしやすくなります。
bash# データベース接続URL(例)
DATABASE_URL="postgresql://user:password@localhost:5432/mydb"
# 環境
NODE_ENV="development"
実際の値は含めず、キー名と形式だけを示します。これはリポジトリにコミットして共有します。
Turborepo との統合
ビルドパフォーマンスをさらに向上させたい場合、Turborepo を導入することをおすすめします。まず、Turborepo をインストールします。
bash# Turborepo のインストール
yarn add -D -W turbo
-W オプションでルートのワークスペースにインストールします。
ルートディレクトリに turbo.json を作成し、パイプラインを定義します。
json{
"$schema": "https://turbo.build/schema.json",
"pipeline": {
"build": {
"dependsOn": ["^build"],
"outputs": ["dist/**"]
}
}
}
dependsOn で依存関係を定義し、outputs でキャッシュ対象を指定します。
Prisma generate をパイプラインに追加します。
json{
"$schema": "https://turbo.build/schema.json",
"pipeline": {
"build": {
"dependsOn": ["^build", "db:generate"],
"outputs": ["dist/**"]
},
"db:generate": {
"cache": false
}
}
}
db:generate タスクは cache: false にすることで、毎回実行されるようにします。これにより、スキーマ変更が確実に反映されます。
ルートの package.json にスクリプトを追加します。
json{
"scripts": {
"build": "turbo run build",
"db:generate": "turbo run db:generate"
}
}
これで yarn build を実行すると、Turborepo が依存関係を解析し、最適な順序でビルドを実行してくれます。
具体例
実際のプロジェクトを想定した、より実践的な例を見ていきましょう。
Next.js と Express API の統合例
フロントエンドに Next.js、バックエンドに Express を使用する構成を考えます。ディレクトリ構成は以下のようになります。
cssmonorepo-root/
├── package.json
├── turbo.json
└── packages/
├── database/
│ ├── prisma/
│ │ └── schema.prisma
│ └── src/
│ └── index.ts
├── api/
│ ├── src/
│ │ ├── index.ts
│ │ └── routes/
│ │ └── users.ts
│ └── package.json
└── web/
├── src/
│ ├── app/
│ │ └── page.tsx
│ └── lib/
│ └── api.ts
└── package.json
この構成により、データベースアクセス層、API 層、UI 層を明確に分離できます。
Express API の実装
Express を使った API サーバーの実装例を示します。packages/api/src/routes/users.ts を作成しましょう。
typescript// 必要なモジュールのインポート
import { Router } from 'express';
import { prisma } from '@monorepo/database';
Express の Router と Prisma Client をインポートします。
ユーザー一覧取得エンドポイントの実装です。
typescript// ルーターの作成
export const usersRouter = Router();
// GET /users - ユーザー一覧取得
usersRouter.get('/', async (req, res) => {
try {
// Prisma でユーザーを取得
const users = await prisma.user.findMany({
select: {
id: true,
email: true,
name: true,
createdAt: true,
},
});
// JSON レスポンスを返す
res.json(users);
} catch (error) {
console.error('ユーザー取得エラー:', error);
res
.status(500)
.json({ error: 'Internal Server Error' });
}
});
select オプションで必要なフィールドだけを取得し、パフォーマンスを最適化しています。エラーハンドリングも適切に行っています。
ユーザー作成エンドポイントの実装です。
typescript// POST /users - ユーザー作成
usersRouter.post('/', async (req, res) => {
try {
const { email, name } = req.body;
// バリデーション
if (!email || typeof email !== 'string') {
return res.status(400).json({
error: 'Email is required and must be a string',
});
}
// ユーザーを作成
const user = await prisma.user.create({
data: { email, name },
});
// 作成したユーザーを返す
res.status(201).json(user);
} catch (error) {
console.error('ユーザー作成エラー:', error);
res
.status(500)
.json({ error: 'Internal Server Error' });
}
});
リクエストボディのバリデーションを行い、適切なステータスコードを返しています。
サーバーのメインファイル packages/api/src/index.ts を実装します。
typescript// Express アプリケーションのセットアップ
import express from 'express';
import { usersRouter } from './routes/users';
const app = express();
const PORT = process.env.PORT || 3001;
環境変数からポート番号を取得し、デフォルト値も設定しています。
ミドルウェアとルーターの設定です。
typescript// ミドルウェアの設定
app.use(express.json()); // JSON パース
app.use(express.urlencoded({ extended: true })); // URL エンコードされたボディをパース
// ルーターの登録
app.use('/api/users', usersRouter);
express.json() で JSON リクエストボディを自動的にパースするよう設定しています。
サーバーの起動処理です。
typescript// サーバー起動
app.listen(PORT, () => {
console.log(
`API サーバーが起動しました: http://localhost:${PORT}`
);
});
これで API サーバーが完成しました。yarn workspace @monorepo/api dev で起動できます。
Next.js からの API 呼び出し
Next.js アプリケーションから API を呼び出す実装例を示します。packages/web/src/lib/api.ts を作成しましょう。
typescript// API クライアントの型定義
import type { User } from '@monorepo/database';
// API ベース URL
const API_BASE_URL =
process.env.NEXT_PUBLIC_API_URL ||
'http://localhost:3001';
環境変数で API の URL を設定できるようにしています。NEXT_PUBLIC_ プレフィックスにより、クライアントサイドでも使用可能になります。
ユーザー一覧取得関数の実装です。
typescript// ユーザー一覧を取得する関数
export async function getUsers(): Promise<User[]> {
// API リクエストを送信
const response = await fetch(
`${API_BASE_URL}/api/users`,
{
method: 'GET',
headers: {
'Content-Type': 'application/json',
},
}
);
// レスポンスチェック
if (!response.ok) {
throw new Error(
`HTTP error! status: ${response.status}`
);
}
// JSON をパース
const users = await response.json();
return users;
}
エラーハンドリングを適切に行い、HTTP ステータスコードもチェックしています。
ユーザー作成関数も実装しましょう。
typescript// ユーザーを作成する関数
export async function createUser(
email: string,
name: string
): Promise<User> {
// API リクエストを送信
const response = await fetch(
`${API_BASE_URL}/api/users`,
{
method: 'POST',
headers: {
'Content-Type': 'application/json',
},
body: JSON.stringify({ email, name }),
}
);
// レスポンスチェック
if (!response.ok) {
const error = await response.json();
throw new Error(
error.message || 'Failed to create user'
);
}
// 作成されたユーザーを返す
const user = await response.json();
return user;
}
リクエストボディを JSON 文字列に変換し、適切なヘッダーを設定しています。
Next.js の Server Component で使用する例です。packages/web/src/app/page.tsx を作成します。
typescript// ユーザー一覧ページ
import { getUsers } from '../lib/api';
export default async function Home() {
// サーバーサイドでユーザーを取得
const users = await getUsers();
return (
<main>
<h1>ユーザー一覧</h1>
<ul>
{users.map((user) => (
<li key={user.id}>
{user.name} ({user.email})
</li>
))}
</ul>
</main>
);
}
Next.js 13+ の Server Component を使用することで、サーバーサイドでデータを取得し、ハイドレーション済みの HTML を返せます。
以下の図は、リクエストの流れを示しています。
mermaidsequenceDiagram
participant Browser as ブラウザ
participant Next as Next.js<br/>(web)
participant API as Express API<br/>(api)
participant Prisma as Prisma Client<br/>(database)
participant DB as PostgreSQL
Browser->>Next: ページアクセス
Next->>API: GET /api/users
API->>Prisma: prisma.user.findMany()
Prisma->>DB: SQL クエリ実行
DB-->>Prisma: ユーザーデータ
Prisma-->>API: User[]
API-->>Next: JSON レスポンス
Next-->>Browser: HTML レンダリング
データの流れが明確になり、各層の責務が分離されています。これにより、保守性とテスタビリティが向上します。
マイグレーションの運用
スキーマ変更を本番環境に適用する際の手順を見ていきましょう。開発環境でのマイグレーション作成方法です。
bash# スキーマを編集した後、マイグレーションを作成
yarn workspace @monorepo/database prisma migrate dev --name add_user_profile
--name オプションでマイグレーションに分かりやすい名前を付けます。これにより、packages/database/prisma/migrations/ ディレクトリにマイグレーションファイルが生成されます。
生成されたマイグレーションファイルの内容を確認します。
sql-- CreateTable
CREATE TABLE "Profile" (
"id" SERIAL NOT NULL,
"bio" TEXT,
"userId" INTEGER NOT NULL,
CONSTRAINT "Profile_pkey" PRIMARY KEY ("id")
);
-- CreateIndex
CREATE UNIQUE INDEX "Profile_userId_key" ON "Profile"("userId");
-- AddForeignKey
ALTER TABLE "Profile" ADD CONSTRAINT "Profile_userId_fkey"
FOREIGN KEY ("userId") REFERENCES "User"("id")
ON DELETE RESTRICT ON UPDATE CASCADE;
Prisma が自動生成した SQL を確認し、意図した通りになっているかチェックします。
本番環境へのデプロイ時は、以下のコマンドを使用します。
bash# 本番環境でマイグレーションを実行
yarn workspace @monorepo/database prisma migrate deploy
migrate deploy は本番環境用のコマンドで、未適用のマイグレーションのみを実行します。開発用の機能(スキーマのリセットなど)は含まれていないため、安全に実行できます。
CI/CD パイプラインの設定
GitHub Actions を使った CI/CD の設定例を示します。.github/workflows/ci.yml を作成しましょう。
yaml# ワークフロー名
name: CI
# トリガー設定
on:
push:
branches: [main, develop]
pull_request:
branches: [main, develop]
main ブランチと develop ブランチへのプッシュ、プルリクエスト作成時にワークフローが実行されます。
ジョブの基本設定です。
yamljobs:
test:
runs-on: ubuntu-latest
# PostgreSQL サービスコンテナ
services:
postgres:
image: postgres:15
env:
POSTGRES_PASSWORD: postgres
POSTGRES_DB: test_db
options: >-
--health-cmd pg_isready
--health-interval 10s
--health-timeout 5s
--health-retries 5
ports:
- 5432:5432
PostgreSQL をサービスコンテナとして起動し、ヘルスチェックも設定しています。
ステップの定義です。
yamlsteps:
# リポジトリをチェックアウト
- uses: actions/checkout@v4
# Node.js のセットアップ
- uses: actions/setup-node@v4
with:
node-version: '20'
cache: 'yarn'
# 依存関係のインストール
- name: Install dependencies
run: yarn install --frozen-lockfile
--frozen-lockfile オプションで、yarn.lock の内容を厳密に再現します。
Prisma の準備とマイグレーション実行です。
yaml# 環境変数の設定
- name: Setup environment
run: |
echo "DATABASE_URL=postgresql://postgres:postgres@localhost:5432/test_db" >> $GITHUB_ENV
# Prisma Client の生成
- name: Generate Prisma Client
run: yarn db:generate
# マイグレーションの実行
- name: Run migrations
run: yarn workspace @monorepo/database prisma migrate deploy
環境変数を設定し、Prisma Client を生成してからマイグレーションを実行します。
ビルドとテストの実行です。
yaml# ビルド実行
- name: Build
run: yarn build
# テスト実行(オプション)
- name: Run tests
run: yarn test
すべてのパッケージをビルドし、テストを実行します。Turborepo を使用している場合、キャッシュが効いて高速に実行されます。
まとめ
Monorepo 環境で Prisma を効果的に活用するためには、パス解決、generate の共有、依存関係の管理という 3 つの重要な要素を適切に設定する必要があります。本記事で紹介した手法を実践することで、以下のメリットが得られます。
まず、パス解決の最適化により、各パッケージから Prisma Client を一貫した方法でインポートできるようになります。@monorepo/database のような明確な名前空間を使用することで、コードの可読性と保守性が向上します。
次に、generate コマンドの実行タイミングを戦略的に管理することで、開発体験が大きく改善されます。postinstall スクリプトを活用すれば、チームメンバー全員が常に最新の Prisma Client を使用でき、「generate を忘れた」というミスを防げます。
さらに、依存関係を適切に宣言し、バージョンを統一することで、予期しないエラーを回避できます。Yarn Workspaces や Turborepo といったツールと組み合わせることで、ビルドパフォーマンスも最適化されます。
実際の開発現場では、Next.js と Express API の組み合わせのように、複数の技術スタックを Monorepo で管理するケースが増えています。database パッケージを中心に据えることで、フロントエンドとバックエンドで型情報を共有し、エンドツーエンドでの型安全性を実現できます。
マイグレーション戦略も重要なポイントです。開発環境では prisma migrate dev を、本番環境では prisma migrate deploy を使い分けることで、安全かつ確実にスキーマ変更を適用できます。CI/CD パイプラインに組み込むことで、デプロイの自動化も実現できます。
Monorepo と Prisma の組み合わせは、最初の設定こそ少し複雑に感じるかもしれませんが、一度構築してしまえば、開発効率が大きく向上します。型安全性、コードの再利用性、保守性のすべてを高いレベルで実現できるのです。
本記事で紹介した設定やコード例を参考に、ぜひ皆さんのプロジェクトでも Monorepo × Prisma の環境を構築してみてください。快適な開発体験を手に入れることができるはずです。
関連リンク
articlePrisma を Monorepo で使い倒す:パス解決・generate の共有・依存戦略
articlePrisma Accelerate と PgBouncer を比較:サーバレス時代の接続戦略ベンチ
articlePrisma で「Cannot find module '@prisma/client'」が出る時の復旧チェックリスト
articlePrisma 読み書き分離設計:読み取りレプリカ/プロキシ/整合性モデルを整理
articlePrisma スキーマ定義チートシート:model/enum/@id/@unique/@index の最短リファレンス
articlePrisma Driver Adapters 導入手順:libSQL/Turso・Neon の最短セットアップ
articleReact でデータ取得を最適化:TanStack Query 基礎からキャッシュ戦略まで実装
articleAnsible Jinja2 テンプレート速攻リファレンス:filters/tests/macros
articlePython Dev Containers 完全レシピ:再現可能な開発箱を VS Code で作る
articleStorybook で Zustand をモックする:Controls 連動とシナリオ駆動 UI
articlePrisma を Monorepo で使い倒す:パス解決・generate の共有・依存戦略
articleプラグイン競合の特定術:WordPress で原因切り分けを高速化する手順
blogiPhone 17シリーズの発表!全モデルiPhone 16から進化したポイントを見やすく整理
blogGoogleストアから訂正案内!Pixel 10ポイント有効期限「1年」表示は誤りだった
blog【2025年8月】Googleストア「ストアポイント」は1年表記はミス?2年ルールとの整合性を検証
blogGoogleストアの注文キャンセルはなぜ起きる?Pixel 10購入前に知るべき注意点
blogPixcel 10シリーズの発表!全モデル Pixcel 9 から進化したポイントを見やすく整理
blogフロントエンドエンジニアの成長戦略:コーチングで最速スキルアップする方法
review今の自分に満足していますか?『持たざる者の逆襲 まだ何者でもない君へ』溝口勇児
reviewついに語られた業界の裏側!『フジテレビの正体』堀江貴文が描くテレビ局の本当の姿
review愛する勇気を持てば人生が変わる!『幸せになる勇気』岸見一郎・古賀史健のアドラー実践編で真の幸福を手に入れる
review週末を変えれば年収も変わる!『世界の一流は「休日」に何をしているのか』越川慎司の一流週末メソッド
review新しい自分に会いに行こう!『自分の変え方』村岡大樹の認知科学コーチングで人生リセット
review科学革命から AI 時代へ!『サピエンス全史 下巻』ユヴァル・ノア・ハラリが予見する人類の未来