DBのテーブルとコードのオブジェクトを対応させる仕組み。SQLの「通訳」として機能し、DB方言の差異を吸収しながら型安全な操作を提供する。
-- 生SQL
SELECT * FROM users WHERE email = 'mickey@example.com' LIMIT 1;
// Prisma
const user = await prisma.user.findFirst({
where: { email: 'mickey@example.com' }
});
// Django ORM
// user = User.objects.filter(email='mickey@example.com').first()
💡 ポイント: SQLの知識がゼロになるわけではなく「書かなくていい場面が増える」
| ORM | 言語 | 型安全 | Edge対応 | SQLへの近さ |
|---|---|---|---|---|
| Prisma | TypeScript | ◎ | ❌ | 低い(隠蔽) |
| Drizzle | TypeScript | ◎ | ✅ | 高い(明示) |
| Django ORM | Python | △ | - | 中程度 |
| Supabase JS | TypeScript | ○ | ✅ | 中程度 |
// Prisma(オブジェクト指向的)
await prisma.user.findUnique({ where: { id: 1 } });
// Drizzle(SQLに近い)
await db.select().from(users).where(eq(users.id, 1));
💡 ポイント: Next.js + Vercel構成ではEdge RuntimeでPrismaが動かないためDrizzleが有利
// CREATE
await db.insert(users).values({ name: 'Mickey', email: 'mickey@example.com' });
// READ
await db.select().from(users).where(eq(users.id, 1));
// UPDATE
await db.update(users).set({ name: 'Mickey2' }).where(eq(users.id, 1));
// DELETE
await db.delete(users).where(eq(users.email, 'test@example.com'));
💡 ポイント: Drizzleは
db.操作(テーブル).条件(eq(カラム, 値))のパターンで統一されている
正規化が進んでJOINが複雑になるほどORMが威力を発揮する。
// Prisma(includeでJOINを隠蔽)
const user = await prisma.user.findUnique({
where: { id: 1 },
include: {
posts: {
include: { comments: true }
}
}
});
// Drizzle(JOINを明示)
const result = await db
.select()
.from(users)
.leftJoin(posts, eq(posts.userId, users.id))
.where(eq(users.id, 1));
💡 ポイント: PrismaはJOINを隠蔽、DrizzleはJOINを明示。SQLに慣れているならDrizzleが読みやすい
スキーマ変更をファイルで管理する仕組み。Gitでコードを管理するようにDBの構造変更も履歴として残す。
# Prisma
npx prisma migrate dev --name add_age_to_users
# Drizzle
npx drizzle-kit generate
npx drizzle-kit migrate
migrations/
├─ 0001_create_users.sql
├─ 0002_add_age_to_users.sql
└─ 0003_create_posts.sql
💡 ポイント: マイグレーションを使わず本番DBを直接変更すると履歴が残らず環境間で差異が生まれる
// N+1問題(❌ Bad)
const users = await prisma.user.findMany();
for (const user of users) {
const posts = await prisma.post.findMany({ where: { userId: user.id } });
// userが100人 → 101回クエリが走る
}
// includeで解決(✅ Good)
const users = await prisma.user.findMany({
include: { posts: true }
});
// 複雑な集計はRaw SQLで(✅ Good)
const result = await prisma.$queryRaw`
SELECT DATE_TRUNC('month', created_at) as month, COUNT(*) as count
FROM orders GROUP BY 1 ORDER BY 1
`;
💡 ポイント: ウィンドウ関数・複雑な集計・パフォーマンスがシビアな箇所はRaw SQLが正解
| ミス | 改善策 |
|---|---|
| ループ内でクエリを実行(N+1) | includeやJOINでまとめて取得 |
| 複雑な集計をORMで書こうとする | Raw SQLに切り替える |
| マイグレーションせず本番DBを直接変更 | 必ずマイグレーションファイルを使う |
| PrismaをEdge Runtimeで使う | DrizzleまたはSupabase JSに切り替える |
- 正規化が進むほどORMの恩恵が大きくなる(JOINの複雑さをORMが吸収)
- PrismaとDrizzleの違いは「SQLの隠蔽度合い」。SQLがわかるならDrizzleが直感的
- N+1問題はORMを使うと気づかず発生しやすい。ループの前に疑う習慣を持つ