prisma-orm について
prisma-ormは、TypeScript向けデータベースツールキットのリファレンススキルです。スキーマ設計、型安全なデータアクセス、マイグレーションに加え、クエリ、リレーション、トランザクション、Prisma Studioやアプリ連携を扱います。
主な機能
- 01スキーマファーストのデータベース設計
- 02型安全なPrisma Clientの生成
- 03CRUD、リレーション、高度なクエリ
- 04開発・本番環境のマイグレーション
- 05トランザクションとクエリ最適化
- 06Prisma Studioでのビジュアルデータ閲覧
活用シーン
- 01TypeScriptアプリのテーブルとリレーション設計
- 02アプリコード向け型安全なデータアクセスの生成
- 03開発・本番環境でのスキーマ変更管理
- 04Prisma Studioでのレコード閲覧、絞り込み、編集
インストール
コマンドをコピーしてターミナルで実行します。
$
npx skills add https:/ / github.com/ bobmatnyc/ claude-mpm-skills --skill prisma-ormインストール後、Claude Code で/prisma-ormを入力して読み込まれたか確認します。
同梱ファイルmetadata.json
詳細
Prisma ORM - 型安全なデータベースツールキット
スキーマファースト開発、自動生成される型安全なクライアント、強力なマイグレーションシステムを備えた、TypeScript 向けの最新データベースツールキットです。
クイックリファレンス
インストール
npm install prisma @prisma/client
npx prisma init
基本的なワークフロー
# 1. Define schema
# Edit prisma/schema.prisma
# 2. Create migration
npx prisma migrate dev --name init
# 3. Generate client
npx prisma generate
# 4. Open Studio
npx prisma studio
コアスキーマパターン
// prisma/schema.prisma
generator client {
provider = "prisma-client-js"
}
datasource db {
provider = "postgresql"
url = env("DATABASE_URL")
}
model User {
id String @id @default(cuid())
email String @unique
name String?
posts Post[]
createdAt DateTime @default(now())
updatedAt DateTime @updatedAt
}
model Post {
id String @id @default(cuid())
title String
content String?
published Boolean @default(false)
author User @relation(fields: [authorId], references: [id])
authorId String
createdAt DateTime @default(now())
updatedAt DateTime @updatedAt
@@index([authorId])
}
型安全な CRUD
import { PrismaClient } from '@prisma/client';
const prisma = new PrismaClient();
// Create
const user = await prisma.user.create({
data: {
email: '[email protected]',
name: 'Alice',
posts: {
create: { title: 'First Post', content: 'Hello World' }
}
},
include: { posts: true }
});
// Read with filters
const users = await prisma.user.findMany({
where: { email: { contains: '@example.com' } },
include: { posts: { where: { published: true } } },
orderBy: { createdAt: 'desc' },
take: 10
});
// Update
await prisma.user.update({
where: { id: userId },
data: { name: 'Bob' }
});
// Delete
await prisma.user.delete({ where: { id: userId } });
スキーマ設計パターン
フィールドの型と属性
model Product {
id Int @id @default(autoincrement())
sku String @unique
name String
description String? // Optional field
price Decimal @db.Decimal(10, 2)
inStock Boolean @default(true)
quantity Int @default(0)
tags String[] // Array field (PostgreSQL)
metadata Json? // JSON field
createdAt DateTime @default(now())
updatedAt DateTime @updatedAt
@@index([sku])
@@index([name, inStock])
}
リレーション
一対多:
model User {
id String @id @default(cuid())
posts Post[]
}
model Post {
id String @id @default(cuid())
author User @relation(fields: [authorId], references: [id])
authorId String
@@index([authorId])
}
多対多:
model Post {
id String @id @default(cuid())
categories Category[] @relation("PostCategories")
}
model Category {
id String @id @default(cuid())
name String @unique
posts Post[] @relation("PostCategories")
}
一対一:
model User {
id String @id @default(cuid())
profile Profile?
}
model Profile {
id String @id @default(cuid())
bio String
user User @relation(fields: [userId], references: [id])
userId String @unique
}
自己リレーション:
model User {
id String @id @default(cuid())
following User[] @relation("UserFollows")
followers User[] @relation("UserFollows")
}
クライアント操作
ネストされた書き込み
// Create with nested relations
const user = await prisma.user.create({
data: {
email: '[email protected]',
profile: {
create: { bio: 'Software Engineer' }
},
posts: {
create: [
{ title: 'Post 1', content: 'Content 1' },
{ title: 'Post 2', content: 'Content 2' }
]
}
}
});
// Update with nested operations
await prisma.user.update({
where: { id: userId },
data: {
posts: {
create: { title: 'New Post' },
update: {
where: { id: postId },
data: { published: true }
},
delete: { id: oldPostId }
}
}
});
トランザクション
逐次(インタラクティブ):
await prisma.$transaction(async (tx) => {
const user = await tx.user.create({
data: { email: '[email protected]' }
});
await tx.post.create({
data: { title: 'Post', authorId: user.id }
});
// Rollback if error thrown
if (someCondition) {
throw new Error('Rollback transaction');
}
});
バッチ(並列):
const [deletedPosts, updatedUser] = await prisma.$transaction([
prisma.post.deleteMany({ where: { published: false } }),
prisma.user.update({
where: { id: userId },
data: { name: 'Updated' }
})
]);
高度なクエリ
集計:
const result = await prisma.post.aggregate({
_count: { id: true },
_avg: { views: true },
_sum: { likes: true },
_max: { createdAt: true },
where: { published: true }
});
const grouped = await prisma.post.groupBy({
by: ['authorId'],
_count: { id: true },
_avg: { views: true },
having: { views: { _avg: { gt: 100 } } }
});
生の SQL:
// Raw query
const users = await prisma.$queryRaw<User[]>`
SELECT * FROM "User" WHERE email LIKE ${`%${search}%`}
`;
// Execute
await prisma.$executeRaw`
UPDATE "Post" SET views = views + 1 WHERE id = ${postId}
`;
マイグレーション
開発ワークフロー
# Create and apply migration
npx prisma migrate dev --name add_user_role
# Reset database (WARNING: deletes all data)
npx prisma migrate reset
# View migration status
npx prisma migrate status
本番環境へのデプロイ
# Apply pending migrations
npx prisma migrate deploy
# Generate client (in CI/CD)
npx prisma generate
スキーマのプロトタイピング
# Push schema without migrations (dev only)
npx prisma db push
# Pull schema from existing database
npx prisma db pull
統合パターン
Next.js App Router
// lib/prisma.ts
import { PrismaClient } from '@prisma/client';
const globalForPrisma = globalThis as unknown as {
prisma: PrismaClient | undefined;
};
export const prisma = globalForPrisma.prisma ?? new PrismaClient();
if (process.env.NODE_ENV !== 'production') {
globalForPrisma.prisma = prisma;
}
サーバーコンポーネント:
// app/users/page.tsx
import { prisma } from '@/lib/prisma';
export default async function UsersPage() {
const users = await prisma.user.findMany({
include: { posts: { take: 5 } }
});
return (
<ul>
{users.map(u => (
<li key={u.id}>{u.name} - {u.posts.length} posts</li>
))}
</ul>
);
}
サーバーアクション:
// app/actions.ts
'use server';
import { prisma } from '@/lib/prisma';
import { revalidatePath } from 'next/cache';
export async function createPost(formData: FormData) {
const title = formData.get('title') as string;
const authorId = formData.get('authorId') as string;
await prisma.post.create({
data: { title, authorId }
});
revalidatePath('/posts');
}
Node.js ミドルウェア
import { PrismaClient } from '@prisma/client';
import express from 'express';
const app = express();
const prisma = new PrismaClient();
app.get('/users/:id', async (req, res) => {
const user = await prisma.user.findUnique({
where: { id: req.params.id },
include: { posts: true }
});
if (!user) return res.status(404).json({ error: 'Not found' });
res.json(user);
});
app.listen(3000);
パフォーマンスの最適化
クエリの最適化
// ❌ N+1 queries
const users = await prisma.user.findMany();
for (const user of users) {
const posts = await prisma.post.findMany({
where: { authorId: user.id }
});
}
// ✅ Single query with include
const users = await prisma.user.findMany({
include: { posts: true }
});
// ✅ Select specific fields
const users = await prisma.user.findMany({
select: { id: true, email: true, posts: { select: { title: true } } }
});
ページネーション
// Cursor-based (recommended for large datasets)
const posts = await prisma.post.findMany({
take: 10,
cursor: lastPostId ? { id: lastPostId } : undefined,
skip: lastPostId ? 1 : 0,
orderBy: { createdAt: 'desc' }
});
// Offset-based (simple but slower)
const posts = await prisma.post.findMany({
skip: (page - 1) * pageSize,
take: pageSize
});
コネクションプーリング
// schema.prisma
datasource db {
provider = "postgresql"
url = env("DATABASE_URL")
// Connection pool settings
directUrl = env("DIRECT_URL")
// Serverless connection limit
relationMode = "prisma" // For PlanetScale, Neon
}
# .env
DATABASE_URL="postgresql://user:pass@host:5432/db?connection_limit=10&pool_timeout=20"
Prisma Studio
# Launch visual database browser
npx prisma studio
機能:
- データを視覚的に閲覧・編集
- レコードの作成、読み取り、更新、削除
- データのフィルタリングと検索
- リレーションを視覚的に表示
localhost:5555で実行
Prisma と Drizzle の比較
| 機能 | Prisma | Drizzle |
|---|---|---|
| スキーマ定義 | 独自 DSL | TypeScript コード |
| 型安全性 | 生成された型 | 推論された型 |
| マイグレーション | 組み込み(migrate) | drizzle-kit |
| クエリビルダー | 流暢な API | SQL 風のビルダー |
| リレーション | 自動 | 手動の結合 |
| Studio | 組み込み GUI | GUI なし |
| バンドルサイズ | ~300kB | ~50kB |
| 生の SQL | サポート | 第一級の機能 |
| Edge Runtime | 制限あり | 完全サポート |
| 学習曲線 | 中程度 | 急 |
| 最適な用途 | フルスタックアプリ、迅速な開発、チーム | エッジ関数、SQL の専門家、バンドルサイズを重視する場合 |
Prisma を選ぶ場合:
- チームがスキーマファースト開発を好む
- データベースの視覚的なツール(Studio)が必要
- リレーションの自動処理が必要
- フルスタックのモノリスを構築する
- 迅速なプロトタイピングとマイグレーションを行う
Drizzle を選ぶ場合:
- 最小限のバンドルサイズが必要(エッジ関数)
- SQL 風の構文を好む
- Edge Runtime にデプロイする(Cloudflare Workers)
- SQL の生成を完全に制御したい
- チームに豊富な SQL の専門知識がある
ベストプラクティス
- シングルトンパターン -
PrismaClientインスタンスを再利用します(特に開発環境) - コネクション管理 - サーバーレス向けにプールサイズを設定します
- 特定のフィールドを選択 -
selectを使ってペイロードサイズを減らします - トランザクションを使用 - 原子性が必要な複数ステップの操作に使用します
- 戦略的にインデックスを作成 - 頻繁にクエリされるフィールドに
@@indexを追加します - マイグレーションを厳格に管理 - デプロイ後にマイグレーションを編集してはいけません
- スキーマのバージョン管理 - 内容が分かるマイグレーション名を使用します
- ソフトデリート - ハード削除の代わりに
deletedAtフィールドを追加します - 保存前に検証 - Prisma の操作前に Zod スキーマを使用します
- クエリを監視 - ログ記録には
prisma.$on('query')を使用します
よくある落とし穴
❌ 複数の PrismaClient インスタンスを作成する:
// WRONG - creates connection leak
function getUser() {
const prisma = new PrismaClient(); // New instance every call
return prisma.user.findMany();
}
// CORRECT - singleton pattern
const prisma = new PrismaClient();
function getUser() {
return prisma.user.findMany();
}
❌ N+1 クエリ:
// WRONG - multiple queries
const users = await prisma.user.findMany();
for (const user of users) {
user.posts = await prisma.post.findMany({ where: { authorId: user.id } });
}
// CORRECT - single query with include
const users = await prisma.user.findMany({ include: { posts: true } });
❌ 複数ステップの操作でトランザクションがない:
// WRONG - not atomic, can leave inconsistent state
await prisma.user.delete({ where: { id: userId } });
await prisma.post.deleteMany({ where: { authorId: userId } }); // May fail
// CORRECT - atomic transaction
await prisma.$transaction([
prisma.post.deleteMany({ where: { authorId: userId } }),
prisma.user.delete({ where: { id: userId } })
]);
注意すべき兆候
次の場合は立ち止まって再検討してください:
- リクエストハンドラー内で新しい
PrismaClientを作成している - 複数ステップの操作でトランザクションを使用していない
- 外部キーや頻繁にクエリされるフィールドにインデックスがない
- 大きなテーブルでページネーションなしに
findManyを使用している - 特定のフィールドだけが必要なのにオブジェクト全体を取得している
- 本番環境での接続エラーを処理していない
- 本番環境で
migrate devを使用している(migrate deployを使用してください)
他のスキルとの連携
- typescript-core:Zod による検証、型安全性のパターン
- nextjs-core:Server Actions、Server Components との統合
- nextjs-v16:App Router のデータ取得、キャッシュ
- database-migration:安全なスキーマ進化のパターン
リソース
- ドキュメント: https://www.prisma.io/docs
- スキーマリファレンス: https://www.prisma.io/docs/reference/api-reference/prisma-schema-reference
- クライアント API: https://www.prisma.io/docs/reference/api-reference/prisma-client-reference
- サンプル: https://github.com/prisma/prisma-examples
関連スキル
Prisma を使用する際、次のスキルがワークフローを強化します。
- drizzle: Prisma の軽量な代替となる Drizzle ORM
- typescript: Prisma の生成型に関する TypeScript のベストプラクティス
- nextjs: Next.js での Prisma:コネクションプーリング、エッジランタイムに関する考慮事項
- test-driven-development: Prisma のモデル、マイグレーション、クエリのテスト
[これらのスキルをバンドルにデプロイすると、完全なドキュメントを利用できます]
コメント
Markdown 対応