api-database-prisma について
TypeScript で Prisma ORM を使う開発者向けの実践ガイドです。型安全なクエリ、スキーマ中心の設計、宣言的なマイグレーションに加え、関連クエリやトランザクション、接続管理を扱います。
主な機能
- 01PrismaClient のシングルトンと接続管理
- 02型安全な CRUD とページネーション
- 03関連クエリと N+1 の防止
- 04スキーマ設計と宣言的マイグレーション
- 05ネスト書き込みとトランザクション
活用シーン
- 01TypeScript アプリの Prisma データアクセス層の構築
- 02モデル、リレーション、インデックス、列挙型の設計
- 03N+1 を避けた関連データの取得
- 04複数の書き込みをトランザクションで実行
インストール
コマンドをコピーしてターミナルで実行します。
npx skills add https:/ / github.com/ agents-inc/ skills --skill api-database-prismaインストール後、Claude Code で/api-database-prismaを入力して読み込まれたか確認します。
同梱ファイル
SKILL.md と一緒にインストールされる参考資料やスクリプト。
metadata.yamlreference.mdexamples/core.mdrelations.mdtransactions.md詳細
翻訳後に原文が更新されています。最新の内容は原文をご確認ください。
Prisma ORM を使用したデータベース
クイックガイド: Prisma ORM を使用すると、型安全なデータベースクエリと自動生成される TypeScript 型を利用できます。宣言的なマイグレーションを備えたスキーマファースト設計です。リレーションには
includeを、アトミック操作には$transactionを使用します。開発時に接続枯渇を防ぐには、シングルトンパターンが必要です。インタラクティブトランザクションのコールバック内では、必ずprismaではなくtxを使用してください。
<critical_requirements>
重要:このスキルを使用する前に
すべてのコードは CLAUDE.md のプロジェクト規約に従う必要があります(kebab-case、名前付きエクスポート、インポート順序、
import type、名前付き定数)
(ホットリロードによる接続枯渇を防ぐため、開発時は必ず PrismaClient にシングルトンパターンを使用してください)
(アトミック性を確保するため、インタラクティブトランザクションのコールバック内では必ず tx パラメーターを使用してください(prisma は使用しないでください))
(リレーショナルクエリでは必ず include またはネストした select を使用してください。同じクエリでリレーションを取得して、N+1 を回避します)
(すべての外部キーリレーションで、明示的な fields と references を指定して @relation を定義してください)
</critical_requirements>
自動検出: prisma、@prisma/client、PrismaClient、prisma.schema、prisma migrate、findUnique、findMany、include、$transaction
使用する場面:
- 自動生成される TypeScript 型を使った型安全なデータベースクエリ
- 宣言的なマイグレーションを用いたスキーマファースト開発
- 強力なリレーショナルデータモデリングが必要なアプリケーション
- Prisma Studio GUI を使った迅速なプロトタイピング
使用しない場面:
- 複雑なクエリで生 SQL のパフォーマンスが必要な場合(Prisma によるオーバーヘッドが発生します)
- コールドスタートを最小限に抑える必要があるエッジ/サーバーレス環境(より軽量な ORM を検討してください)
- TypeScript 以外のプロジェクト(主なメリットを活用できません)
- 生成される SQL を細かく制御する必要がある場合
主な対象パターン:
- PrismaClient シングルトン(開発時のホットリロード対策)
- 型安全なフィルターとページネーションを使った CRUD 操作
includeとネストしたselectによるリレーショナルクエリ- トランザクション(ネストした書き込み、バッチ、インタラクティブ)
- スキーマ設計(モデル、リレーション、列挙型、インデックス)
詳細なリソース:
- examples/core.md - シングルトンの設定、CRUD、フィルタリング、ページネーション
- examples/relations.md - リレーショナルクエリ、include、N+1 の防止
- examples/transactions.md - アトミック操作、インタラクティブトランザクション、エラー処理
- reference.md - 判断基準、アンチパターン、パフォーマンス
基本理念
Prisma ORM は、型安全なデータベースクライアントを生成する宣言的なスキーマ言語を提供します。スキーマは、データモデル、TypeScript 型、マイグレーションにおける単一の信頼できる情報源となります。
基本原則:
- スキーマファースト設計 -
schema.prismaでモデルを定義し、それ以外をすべて生成します - あらゆる場面での型安全性 - すべてのクエリはスキーマに基づいて完全に型付けされます
- 宣言的なマイグレーション - スキーマの変更からマイグレーション SQL が自動的に生成されます
- 直感的な API - クエリは英語のように読みやすく記述できます(
prisma.user.findMany())
基本パターン
パターン 1:PrismaClient シングルトン
開発時のホットリロード中に接続プールが枯渇するのを防ぐには、シングルトンパターンを使用します。これを使用しない場合、ホットリロードのたびに独自の接続プールを持つ新しい PrismaClient が作成され、データベース接続がすぐに枯渇します。
// lib/db/client.ts
import { PrismaClient } from "@prisma/client";
const globalForPrisma = globalThis as unknown as {
prisma: PrismaClient | undefined;
};
const createPrismaClient = () => {
return new PrismaClient({
log:
process.env.NODE_ENV === "development"
? ["query", "error", "warn"]
: ["error"],
});
};
export const prisma = globalForPrisma.prisma ?? createPrismaClient();
if (process.env.NODE_ENV !== "production") {
globalForPrisma.prisma = prisma;
}
利点: globalThis はホットリロードをまたいで保持され、ログを条件付きにすることで本番環境での不要な出力を避けられます
サーバーレス環境での接続パターンについては、examples/core.md を参照してください。
パターン 2:スキーマ設計
リレーション、制約、デフォルト値を含むモデルを定義します。スキーマが信頼できる唯一の情報源です。
model User {
id String @id @default(cuid())
email String @unique
name String?
role Role @default(USER)
posts Post[]
profile Profile?
createdAt DateTime @default(now())
updatedAt DateTime @updatedAt
@@map("users")
}
model Post {
id String @id @default(cuid())
title String
content String?
published Boolean @default(false)
author User @relation(fields: [authorId], references: [id], onDelete: Cascade)
authorId String
createdAt DateTime @default(now())
updatedAt DateTime @updatedAt
@@index([authorId])
@@map("posts")
}
利点: 衝突耐性のある ID には cuid()、変更の自動追跡には @updatedAt、孤立レコードの防止には onDelete: Cascade を指定した @relation、外部キーへの @@index、コード上では PascalCase のままデータベーステーブルを snake_case にするには @@map を使用します
パターン 3:型安全なフィルターを使用した CRUD
すべてのクエリはスキーマに基づいて完全に型付けされています。主な操作:
const DEFAULT_PAGE_SIZE = 20;
const MAX_PAGE_SIZE = 100;
// Find by unique field - returns T | null
const user = await prisma.user.findUnique({
where: { email: "[email protected]" },
});
// Find many with filters + pagination
const users = await prisma.user.findMany({
where: {
role: { in: ["USER", "MODERATOR"] },
createdAt: { gte: new Date("2024-01-01") },
},
orderBy: { name: "asc" },
take: DEFAULT_PAGE_SIZE,
});
// Upsert - atomic create-or-update
const upserted = await prisma.user.upsert({
where: { email: "[email protected]" },
create: { email: "[email protected]", name: "Alice" },
update: { name: "Alice Updated" },
});
優れている理由: 型安全な操作によりコンパイル時にエラーを検出でき、findUnique は T | null を返すため null の処理が必要になり、upsert はアトミックです。
CRUD 操作、フィルタリング、ページネーションのパターンの詳細は、examples/core.md を参照してください。
パターン 4:リレーショナルクエリ
include またはネストした select を使用して、N+1 クエリを回避しながら関連データを効率的に取得します。
// Include related records - single query
const userWithPosts = await prisma.user.findUnique({
where: { id: userId },
include: {
posts: {
where: { published: true },
orderBy: { createdAt: "desc" },
take: 10,
},
profile: true,
},
});
// Select specific fields only - smaller payload
const userSummary = await prisma.user.findUnique({
where: { id: userId },
select: {
id: true,
name: true,
posts: { select: { id: true, title: true } },
},
});
優れている理由: 1 回のクエリで N+1 を回避でき、include はすべてのフィールドを取得し、select はペイロードを削減します。レコードごとにクエリをループさせないでください。代わりに include または select を使用します。
リレーションフィルター、多対多、自己リレーション、include と select の比較、N+1 のアンチパターンについては、examples/relations.md を参照してください。
パターン 5:トランザクション
複数の書き込みにまたがるアトミックな操作を保証します。利用できる種類は 3 つあります。
// Nested writes - implicit transaction (cleanest for related records)
const user = await prisma.user.create({
data: {
email: "[email protected]",
name: "Alice",
profile: { create: { bio: "Developer" } },
posts: { create: [{ title: "First Post", published: true }] },
},
include: { profile: true, posts: true },
});
// Interactive transaction - ALWAYS use tx, never prisma
return await prisma.$transaction(async (tx) => {
const sender = await tx.account.update({
where: { id: fromId },
data: { balance: { decrement: amount } },
});
if (sender.balance < MINIMUM_BALANCE) throw new Error("Insufficient funds");
return await tx.account.update({
where: { id: toId },
data: { balance: { increment: amount } },
});
});
優れている理由: ネストした書き込みで関連レコードを処理でき、インタラクティブトランザクションでは自動ロールバックを伴うビジネスロジックを実装できます。コールバック内で tx の代わりに prisma を使用すると、トランザクションコンテキストを迂回します。
バッチトランザクション、エラー処理、楽観的同時実行制御、トランザクションオプションについては、examples/transactions.md を参照してください。
パターン 6:接続管理
接続リークを防ぐため、beforeExit で $disconnect() を処理します。サーバーレス環境では、別の DATABASE_URL_WITH_POOLER 環境変数を使って接続プーラー(PgBouncer、Prisma Accelerate)を利用します。
グレースフルシャットダウンとサーバーレスの接続パターンについては、examples/core.md を参照してください。
<red_flags>
要注意事項
優先度の高い問題:
- インポートのたびに PrismaClient を作成すること。ホットリロード中にデータベース接続を使い果たします
- インタラクティブトランザクション内で
txの代わりにprismaを使用すること。トランザクションコンテキストを迂回します - ループ内で N+1 クエリを実行すること。代わりに
includeまたはselectを使用します @relation属性がないこと。曖昧な外部キーによりマイグレーションエラーが発生します
優先度が中程度の問題:
- 頻繁にフィルタリングするカラムにインデックスがないこと。データの増加に伴いクエリが遅くなります
- 大きなテーブルでオフセットページネーションを使用すること。パフォーマンスが線形に低下します
onDeleteのカスケード設定がないこと。親レコードの削除時に孤立レコードが残ります- 必要なフィールドが一部だけなのに、
includeですべてのフィールドを取得すること。selectを使用します
注意点とエッジケース:
createManyは作成したレコードを返しません(PostgreSQL/CockroachDB/SQLite ではcreateManyAndReturnを使用します)updateManyとdeleteManyは@updatedAtフィールドを自動更新しません- 暗黙的な多対多テーブルには追加フィールドを持たせられません。明示的な結合モデルを使用します
Jsonフィールドの型はJsonValueです。パース境界でランタイム検証が必要ですDecimalフィールドはPrisma.Decimal型を返します。.toNumber()で変換します- インタラクティブトランザクションのデフォルトのタイムアウトは 5s です。
timeoutオプションで延長します - インタラクティブトランザクションはデータベース接続を保持します。短時間で完了させます
orderByを指定しないfindFirstは、結果の順序が不定です- Enum の変更で値を追加・削除するには、マイグレーションが必要です
</red_flags>
<critical_reminders>
重要な注意事項
すべてのコードは CLAUDE.md のプロジェクト規約に従う必要があります
(ホットリロードによる接続の枯渇を防ぐため、開発時は必ず PrismaClient にシングルトンパターンを使用してください)
(アトミック性を確保するため、インタラクティブトランザクションのコールバック内では必ず tx パラメーターを使用し、prisma は使用しないでください)
(N+1 を避けるため、リレーショナルクエリでは必ず include またはネストした select を使用し、同じクエリでリレーションを取得してください)
(すべての外部キーリレーションでは、fields と references を明示して @relation を必ず定義してください)
これらのルールに従わないと、データベース接続が枯渇し、トランザクションのアトミック性が損なわれ、N+1 によるパフォーマンス問題が発生し、リレーション定義が曖昧になります。
</critical_reminders>