← ブログ一覧

Prisma vs Drizzle ORM:実務選定ガイド

型安全な ORM をどう選ぶ?移行コスト・パフォーマンス・開発体験を比較し、プロジェクト特性による判断基準、マイグレーション戦略、実装パターンまで受託開発で即使える実践ガイド。

#TypeScript#データベース#PostgreSQL#技術解説
Prisma vs Drizzle ORM:実務選定ガイド

Prisma vs Drizzle ORM:実務選定ガイド

TypeScript で型安全なデータベースアクセスを実現する ORM として、Prisma と Drizzle ORM が注目されています。受託開発や自社開発の現場で「どちらを選ぶべきか」「既存プロジェクトから移行すべきか」という判断に迷うケースが増えています。

この記事では、両者の設計思想・パフォーマンス・開発体験を比較し、プロジェクト特性に応じた選定基準と移行戦略を実務視点で解説します。


1. Prisma と Drizzle ORM の特徴比較

Prisma の特徴

  • スキーマファースト設計.prisma ファイルで DB スキーマを定義し、TypeScript 型を自動生成
  • 豊富なツール群:Prisma Studio(GUI)、マイグレーション、シード機能が標準装備
  • 抽象化レベルが高い:SQL を意識せず CRUD 操作が可能
  • エコシステムが成熟:Next.js / tRPC など主要フレームワークとの統合例が豊富

Drizzle ORM の特徴

  • SQL ライク設計:TypeScript でスキーマを定義し、SQL に近い API で操作
  • 軽量・高速:ランタイムオーバーヘッドが小さく、バンドルサイズも最小
  • 生 SQL との親和性:複雑なクエリも SQL に近い形で記述可能
  • マイグレーション柔軟性:Drizzle Kit で自動生成 or 手動 SQL 両対応

主要な違い

| 項目 | Prisma | Drizzle ORM | |------|--------|-------------| | スキーマ定義 | .prisma ファイル(DSL) | TypeScript コード | | 型生成 | 自動生成(クライアント再生成必要) | 推論ベース(定義=型) | | クエリビルダー | 抽象化されたメソッド | SQL ライクな API | | パフォーマンス | 中程度のオーバーヘッド | 軽量・高速 | | バンドルサイズ | 約 500KB(gzip 後) | 約 40KB(gzip 後) | | GUI ツール | Prisma Studio あり | なし(CLI のみ) | | 学習コスト | 低(SQL 知識不要) | 中(SQL 理解推奨) | | エッジ対応 | Data Proxy 経由で可能 | ネイティブ対応 |


2. プロジェクト特性による選定基準

Prisma を選ぶべきケース

✅ チーム全員が SQL に不慣れ

// Prisma: SQL を意識しない直感的な API
const users = await prisma.user.findMany({
  where: {
    posts: {
      some: {
        published: true,
      },
    },
  },
  include: {
    posts: true,
  },
});

✅ 開発速度を最優先したい MVP

  • Prisma Studio で DB を GUI 確認可能
  • マイグレーション・シードが一貫したツールチェーン
  • Next.js などとの統合例が豊富で導入が早い

✅ 複雑な DB 操作が少ないアプリ

  • CRUD 中心の業務アプリ
  • リレーションが浅い(2〜3 階層程度)

Drizzle ORM を選ぶべきケース

✅ パフォーマンスが重要

// Drizzle: SQL に近い記述でパフォーマンスチューニング可能
import { eq, and, sql } from 'drizzle-orm';

const users = await db
  .select({
    id: users.id,
    name: users.name,
    postCount: sql<number>`count(${posts.id})`,
  })
  .from(users)
  .leftJoin(posts, eq(users.id, posts.userId))
  .where(and(eq(posts.published, true)))
  .groupBy(users.id);

✅ エッジランタイムで動かしたい

  • Cloudflare Workers / Vercel Edge Functions など
  • バンドルサイズが厳しい制約になる環境

✅ 複雑な SQL を多用する

  • 集計クエリ・サブクエリが頻繁に登場
  • 生 SQL を混在させながら型安全性を保ちたい

3. 実装パターン比較

スキーマ定義

Prisma

// prisma/schema.prisma
model User {
  id        Int      @id @default(autoincrement())
  email     String   @unique
  name      String?
  posts     Post[]
  createdAt DateTime @default(now())
}

model Post {
  id        Int      @id @default(autoincrement())
  title     String
  published Boolean  @default(false)
  author    User     @relation(fields: [authorId], references: [id])
  authorId  Int
}
# 型生成
npx prisma generate

Drizzle ORM

// db/schema.ts
import { pgTable, serial, text, boolean, timestamp, integer } from 'drizzle-orm/pg-core';
import { relations } from 'drizzle-orm';

export const users = pgTable('users', {
  id: serial('id').primaryKey(),
  email: text('email').notNull().unique(),
  name: text('name'),
  createdAt: timestamp('created_at').defaultNow(),
});

export const posts = pgTable('posts', {
  id: serial('id').primaryKey(),
  title: text('title').notNull(),
  published: boolean('published').default(false),
  authorId: integer('author_id').notNull().references(() => users.id),
});

export const usersRelations = relations(users, ({ many }) => ({
  posts: many(posts),
}));

export const postsRelations = relations(posts, ({ one }) => ({
  author: one(users, { fields: [posts.authorId], references: [users.id] }),
}));

型は定義から自動推論されるため、generate 不要。

データ取得パターン

リレーション付き取得

Prisma

const userWithPosts = await prisma.user.findUnique({
  where: { id: 1 },
  include: {
    posts: {
      where: { published: true },
      orderBy: { createdAt: 'desc' },
      take: 10,
    },
  },
});

Drizzle ORM

import { eq, desc } from 'drizzle-orm';

const userWithPosts = await db.query.users.findFirst({
  where: eq(users.id, 1),
  with: {
    posts: {
      where: eq(posts.published, true),
      orderBy: [desc(posts.createdAt)],
      limit: 10,
    },
  },
});

集計クエリ

Prisma

const postCount = await prisma.post.count({
  where: { published: true },
});

const avgViews = await prisma.post.aggregate({
  _avg: { views: true },
  where: { published: true },
});

Drizzle ORM

import { count, avg, sql } from 'drizzle-orm';

const [result] = await db
  .select({
    count: count(),
    avgViews: avg(posts.views),
  })
  .from(posts)
  .where(eq(posts.published, true));

トランザクション

Prisma

await prisma.$transaction(async (tx) => {
  const user = await tx.user.create({
    data: { email: 'test@example.com', name: 'Test' },
  });
  
  await tx.post.create({
    data: {
      title: 'First Post',
      authorId: user.id,
    },
  });
});

Drizzle ORM

await db.transaction(async (tx) => {
  const [user] = await tx.insert(users).values({
    email: 'test@example.com',
    name: 'Test',
  }).returning();
  
  await tx.insert(posts).values({
    title: 'First Post',
    authorId: user.id,
  });
});

4. マイグレーション戦略

Prisma のマイグレーション

# 開発環境:スキーマ変更を即反映
npx prisma db push

# 本番用マイグレーション生成
npx prisma migrate dev --name add_user_role

# 本番適用
npx prisma migrate deploy

特徴

  • マイグレーション履歴を migrations/ ディレクトリで管理
  • SQL ファイルを直接編集可能(カスタマイズ対応)
  • Rollback は手動で SQL 実行が必要

Drizzle ORM のマイグレーション

# スキーマから差分 SQL 生成
npx drizzle-kit generate:pg

# マイグレーション適用
npx drizzle-kit push:pg

# または Node.js で実行
node -r esbuild-register migrate.ts
// migrate.ts
import { drizzle } from 'drizzle-orm/node-postgres';
import { migrate } from 'drizzle-orm/node-postgres/migrator';
import { Pool } from 'pg';

const pool = new Pool({ connectionString: process.env.DATABASE_URL });
const db = drizzle(pool);

await migrate(db, { migrationsFolder: './drizzle' });
await pool.end();

特徴

  • TypeScript スキーマから SQL を自動生成
  • 生成された SQL を確認・編集可能
  • 手動 SQL とツール生成を混在可能

5. パフォーマンス比較

ベンチマーク結果(参考値)

| 操作 | Prisma | Drizzle ORM | 生 SQL | |------|--------|-------------|--------| | 単純 SELECT(1000件) | 45ms | 12ms | 10ms | | JOIN 付き SELECT | 78ms | 28ms | 25ms | | INSERT(100件) | 120ms | 35ms | 30ms | | トランザクション | 95ms | 40ms | 38ms |

※ PostgreSQL、Node.js 20、ローカル環境での計測例

パフォーマンス差の要因

Prisma のオーバーヘッド

  1. クエリエンジン経由:Rust 製エンジンとの通信コスト
  2. 型変換処理:DB 型から TypeScript 型への変換レイヤー
  3. 追加クエリ発行:リレーション解決で暗黙的な追加クエリが発生する場合あり

Drizzle ORM の最適化

  1. 直接 SQL 生成:最小限の変換で DB ドライバーに渡す
  2. ゼロコストリレーション:JOIN を明示的に制御可能
  3. プリペアドステートメント:自動で再利用

実務での影響

  • 小〜中規模アプリ:体感差はほぼなし(100ms 以下の差)
  • 大量データ処理:Drizzle が 2〜3 倍高速なケースあり
  • エッジ環境:バンドルサイズの差が起動時間に影響

6. 開発体験(DX)の違い

Prisma の強み

✅ GUI ツール(Prisma Studio)

npx prisma studio

ブラウザで DB をテーブル形式で確認・編集可能。開発中のデータ確認が楽。

✅ 自動補完が強力

const user = await prisma.user.findUnique({
  where: { id: 1 },
  // ↓ include を入力すると、リレーション先が自動で候補表示
  include: {
    posts: true, // ← posts が補完される
  },
});

✅ エラーメッセージが親切

Invalid `prisma.user.create()` invocation:

  Unique constraint failed on the fields: (`email`)

Drizzle ORM の強み

✅ TypeScript ネイティブ

// スキーマ定義がそのまま型として使える
import { users } from './schema';
type User = typeof users.$inferSelect;
type NewUser = typeof users.$inferInsert;

✅ 生 SQL との混在が自然

const result = await db.execute(sql`
  SELECT u.*, COUNT(p.id) as post_count
  FROM users u
  LEFT JOIN posts p ON u.id = p.author_id
  WHERE ${eq(u.active, true)}
  GROUP BY u.id
`);

✅ バンドルサイズが小さい

// package.json
{
  "dependencies": {
    "drizzle-orm": "^0.30.0",  // ~40KB
    "pg": "^8.11.0"
  }
}

Vercel Edge Functions など厳しい制約でも導入可能。


7. 移行戦略とチェックリスト

Prisma → Drizzle ORM への移行

ステップ 1:既存スキーマを Drizzle 形式に変換

# Prisma スキーマから SQL をエクスポート
npx prisma db pull
npx prisma migrate diff --from-empty --to-schema-datamodel prisma/schema.prisma --script > schema.sql

# Drizzle で既存 DB から型生成(introspection)
npx drizzle-kit introspect:pg

ステップ 2:段階的置き換え

// 1. 両方のクライアントを共存させる
import { PrismaClient } from '@prisma/client';
import { drizzle } from 'drizzle-orm/node-postgres';
import { Pool } from 'pg';

const prisma = new PrismaClient();
const pool = new Pool({ connectionString: process.env.DATABASE_URL });
const db = drizzle(pool);

// 2. 新規実装は Drizzle、既存は Prisma のまま
export { prisma, db };

ステップ 3:テストカバレッジ確認

// 移行前後で同じ結果が返ることを確認
import { expect, test } from 'vitest';

test('Prisma と Drizzle で同じデータ取得', async () => {
  const prismaUsers = await prisma.user.findMany();
  const drizzleUsers = await db.select().from(users);
  
  expect(drizzleUsers.length).toBe(prismaUsers.length);
});

Drizzle ORM → Prisma への移行

稀なケースだが、チームの SQL スキル不足で Prisma に戻すケース。

ステップ 1:Prisma スキーマ生成

# 既存 DB から Prisma スキーマ生成
npx prisma db pull

ステップ 2:クエリの抽象化

// Drizzle の複雑なクエリを Prisma で書き直す
// Before (Drizzle)
const users = await db
  .select()
  .from(users)
  .leftJoin(posts, eq(users.id, posts.authorId))
  .where(eq(posts.published, true));

// After (Prisma)
const users = await prisma.user.findMany({
  where: {
    posts: {
      some: { published: true },
    },
  },
  include: { posts: true },
});

8. 実務チェックリスト

Prisma 採用時の確認項目

| 項目 | チェック内容 | |------|-------------| | バンドルサイズ制約 | エッジ環境で動かす予定はないか? | | 複雑クエリ頻度 | 集計・サブクエリが多用されるか? | | チーム SQL スキル | メンバー全員が SQL を書けるか? | | GUI ツール依存度 | Prisma Studio なしで開発可能か? | | Data Proxy コスト | エッジ環境で使う場合、追加費用を許容できるか? |

Drizzle ORM 採用時の確認項目

| 項目 | チェック内容 | |------|-------------| | SQL 知識 | チームに SQL に詳しいメンバーがいるか? | | パフォーマンス要件 | レスポンス時間がシビアか? | | GUI 不要 | CLI だけで DB 確認・編集できるか? | | エコシステム | 必要なライブラリの Drizzle 対応状況は? | | 学習コスト | メンバーが SQL ライクな API を習得する時間があるか? |


まとめ

Prisma はこんなプロジェクトに最適

  • 開発速度重視の MVP・スタートアップ
  • SQL に不慣れなチーム
  • GUI ツールで開発効率を上げたい
  • Next.js など主流フレームワークとの統合例が豊富

Drizzle ORM はこんなプロジェクトに最適

  • パフォーマンスがクリティカル
  • エッジ環境で動かす
  • 複雑な SQL を多用する
  • バンドルサイズを最小化したい

移行判断のポイント

  • 既存 Prisma プロジェクトは、パフォーマンス問題が顕在化してから検討
  • 新規プロジェクトは、チームスキル・実行環境・クエリ複雑度で判断
  • 両方のクライアントを共存させて段階移行も可能

どちらも型安全で優れた ORM です。プロジェクトの特性とチームの強みに合わせて選択しましょう。


Yureate では、受託開発プロジェクトの技術選定から実装まで一貫してサポートしています。ORM 選定や既存システムの移行でお困りの際は、お気軽にご相談ください。

この内容について相談する他の記事を見る