言語を切り替える
テーマを切り替える

Drizzle ORM 実戦ガイド:Prismaより90%軽量なTypeScript ORMの選択

先週、プロジェクトをVercelにデプロイし、トップページを開いたら3秒も待たされました。3秒です! ユーザーならとっくに離脱しています。

Vercelのバンドル分析を開き、14MBも占めているPrisma Clientのパッケージを見つめながら、私は人生を疑い始めました。ただの単純なユーザー検索なのに、なぜこんなに大量のコードを読み込む必要があるのでしょうか? さらに悪いことに、Lambdaのコールドスタート時間が狂うほど長かったのです——Prismaを使っていない関数は600msで立ち上がるのに、Prismaを使った関数は2.5秒もかかっていました。

「でもPrismaは便利じゃないか?」とあなたは言うかもしれません。確かに、私もそう思っていました。型安全性、自動マイグレーション、Prisma Studioという可視化ツール、必要なものはすべて揃っています。しかし問題は——**「重すぎる」**ことです。

もしあなたも、サーバーレス環境でのコールドスタートの遅さ、バンドルサイズの肥大化、複雑なクエリの制御不能(結局raw SQLを書く羽目になる)といった問題に直面しているなら、この記事はあなたのためのものです。今日紹介する Drizzle ORM は、コアパッケージがわずか7.4kbで、Prismaより90%以上軽量でありながら、同等の型安全性を提供します。

この記事では、Next.js + Drizzleのゼロからの設定、SQLライクなAPIの使い方、そしてDrizzleとPrismaの徹底的な性能比較を行います。最も重要なこととして、どのようなシナリオでDrizzleを選び、どのようなシナリオではまだPrismaが優れているのかをお伝えします。

なぜDrizzleが必要なのか? 既存ORMの痛点

Prismaの3大痛点

正直なところ、Prismaは多くの場面で優れた選択肢です。しかし長く使っていると、避けて通れない問題が出てきます。

痛点1:バンドルサイズの制御不能

Prisma v5の生成済みClientは14MBに達することがあります。これがどういうことかというと、Next.jsプロジェクト全体が2-3MBかもしれないのに、Prismaだけでその半分近くを占める可能性があるということです。Prisma 7ではRustバイナリを削除して1MBまで最適化されましたが、古いバージョンを使っていたり、バンドルサイズに敏感なプロジェクトでは大きな問題です。

私たちのチームにはCloudflare Workersにデプロイするリアルタイムチャットアプリがあり、バンドルサイズの上限は1MBでした。Prismaでは完全にオーバーしていたため、最終的に別のソリューションに乗り換えるしかありませんでした。

痛点2:サーバーレスでの遅いコールドスタート

GitHubにはIssue #10724という数年来の議論があります。PrismaのLambdaコールドスタートが遅すぎる問題です。データは明白です:

  • ORMなしの関数:~600ms
  • Prisma v5使用の関数:~2.5s
  • Prisma v7使用の関数:~1.5s(改善されたが、まだ遅い)

この背後にある理由は、Prismaが起動時に巨大なDMMF(Data Model Meta Format)文字列を解析する必要があるためです。中規模のスキーマでもこの文字列は600万文字以上になります。コールドスタートのたびにこれを解析するのですから、遅くて当然です。

Cal.com(オープンソースのカレンダー予約ツール)もこの問題について技術ブログを書き、コールドスタート時間をどう最適化したか共有しています。結論として、問題は確実に存在し、様々なワークアラウンドで緩和するしかありませんでした。

痛点3:SQL制御力の不足

Prismaの設計哲学は「SQLの抽象化」であり、Prisma ClientのDSLを使ってクエリを書かせようとします。大抵は便利ですが、複雑なクエリになると厄介です。

例えば、多層JOIN + サブクエリ + 条件付き集計のような複雑なクエリを書こうとすると、PrismaのAPIでは表現できないことがあります。結局 prisma.$queryRaw を使って直接SQLを書くことになります。

それなら、最初からSQLライクなAPIを使えばいいのでは? それがDrizzleの発想です。

開発者のリアルな需要

開発者が本当に求めているものをまとめると:

  1. 型安全性、しかし性能は犠牲にしない:TypeScriptの型推論は欲しいが、ORMのせいでプロジェクトが遅くなるのは困る
  2. SQLを直接使う、新しいDSLは覚えない:SQL自体で十分だ、Prisma独自のクエリ構文を覚えさせないでくれ
  3. サーバーレスフレンドリー:2025年にもなって、Vercel、Cloudflare Workers、AWS Lambdaを使わない手はない
  4. コンパイル速度:大規模プロジェクトではPrismaの型推論がTypeScriptのコンパイルを遅くする、これも隠れたコストだ

Drizzleはまさにこれらの需要に応えるために生まれました。

Drizzle ORMとは? 核心機能の解析

Drizzleの設計哲学

Drizzleのスローガンは “If you know SQL, you know Drizzle”(SQLを知っていれば、Drizzleもわかる)。強気ですが、确实にその通りです。

"If you know SQL, you know Drizzle."

従来のORMはSQLを隠蔽しようとしますが、Drizzleはその逆を行きます——SQLを抽象化せず、TypeScript APIを可能な限りSQL構文に近づけるのです。書くコードはSQLのように見えますが、完全な型ヒントがあります。

例を見れば一目瞭然です:

// Drizzle クエリ
await db
  .select()
  .from(posts)
  .leftJoin(comments, eq(posts.id, comments.postId))
  .where(eq(posts.id, 10))

// 生成される SQL
SELECT * FROM posts
LEFT JOIN comments ON posts.id = comments.post_id
WHERE posts.id = 10

見ての通り、コード構造はSQLとほぼ同じです。SQLがわかる人なら、一目で使い方がわかります。

核心機能

1. 極限の軽量化

コアパッケージ drizzle-orm はわずか 7.4kb(min+gzip)で、ランタイム依存ゼロです。比較してみましょう:

  • Drizzle:~7.4kb
  • TypeORM:~300kb
  • Prisma v7:~1MB
  • Prisma v5:~14MB

これは桁違いの差です。

2. TypeScriptファースト、Client生成不要

Prismaは prisma generate を実行してClientを生成する必要がありますが、Drizzleは不要です。

スキーマを定義すれば、TypeScriptが直接型を推論します。IntelliSenseの自動補完、型チェック、エラー提示、すべて揃っています。コンパイル時に問題を発見でき、実行時まで待つ必要はありません。

3. SQLライクAPI、学習コストほぼゼロ

SQLが書ければ、Drizzleの習得に10分もかかりません。

// SELECT クエリ
db.select().from(users).where(eq(users.id, 1))

// INSERT 挿入
db.insert(users).values({ name: 'John', email: '[email protected]' })

// UPDATE 更新
db.update(users).set({ name: 'Jane' }).where(eq(users.id, 1))

// DELETE 削除
db.delete(users).where(eq(users.id, 1))

SQLを知っている人がこのコードを見れば、ドキュメントを調べる必要すらありません。

4. 性能劣化なし

Drizzleにはランタイムの抽象化層がありません。書いたクエリは直接SQLに変換され、中間プロセスはありません。

Prismaが起動時にDMMFを解析し内部状態を維持するのと対照的に、Drizzleは純粋なクエリビルダーです。隠れたコストも、予期せぬオーバーヘッドもありません。

5. サーバーレス対応

Drizzleはすべての主要なサーバーレス環境をサポートしています:

  • Vercel Edge Functions
  • Cloudflare Workers
  • AWS Lambda
  • Deno Deploy
  • Bun

さらにサーバーレスデータベースドライバもネイティブサポート:

  • Neon Serverless
  • PlanetScale
  • Turso(SQLite on the edge)
  • Supabase

Neon + DrizzleでVercel Edgeにデプロイした私たちのプロジェクトでは、コールドスタート時間が2.5秒から700msに短縮されました。これが実際の効果です。

適用シナリオ

Drizzleは万能ではありませんが、以下のシナリオでは特に適しています:

1. サーバーレスアプリケーション

LambdaやEdge Functionsのような環境で動かすなら、Drizzleの軽さと高速なコールドスタートは必須要件です。

2. 性能重視のシナリオ

リアルタイムアプリ、金融システム、データ分析プラットフォーム——クエリ遅延に敏感なあらゆるシナリオで、Drizzleのゼロ抽象化設計は実質的な性能向上をもたらします。

3. 複雑なSQL制御が必要なプロジェクト

大量の複雑なクエリがあり、手動でSQL最適化が必要な場合、DrizzleのSQLライクAPIはPrismaのDSLより遥かに使いやすいです。

4. バンドルサイズに敏感なフロントエンドプロジェクト

一部のフルスタックフレームワーク(SolidStart、Qwikなど)はORMコードをクライアントにバンドルすることがあります。この時、Drizzleの7.4kbは巨大な利点になります。

逆に、チームがSQLに不慣れで、プロトタイプを爆速開発したい、あるいはPrismaのエコシステム(Prisma Studio、Prisma Migrate、Prisma Pulse)を好むなら、Prismaの方がまだ良い選択かもしれません。

Next.js + Drizzle 実戦設定

理論はこれくらいにして、実践に入りましょう。Next.js 15 + Drizzle + PostgreSQLのプロジェクトをゼロから設定する方法を解説します。

環境準備

まずNext.jsプロジェクトを作成:

npx create-next-app@latest my-drizzle-app
cd my-drizzle-app

Drizzle関連の依存関係をインストール:

npm install drizzle-orm drizzle-kit
npm install @neondatabase/serverless  # Neonデータベースなら
# または
npm install postgres  # 従来のPostgreSQLなら

ここでは Neon を推奨します。Drizzleと相性抜群のサーバーレスPostgreSQLです。無料アカウントを作り、データベースを作成して接続文字列を取得してください。

データベースSchemaの定義

db/schema.ts を作成し、テーブル構造を定義します:

import { pgTable, serial, text, timestamp, integer } from 'drizzle-orm/pg-core';
import { relations } from 'drizzle-orm';

// ユーザーテーブル
export const users = pgTable('users', {
  id: serial('id').primaryKey(),
  name: text('name').notNull(),
  email: text('email').notNull().unique(),
  createdAt: timestamp('created_at').defaultNow(),
});

// 記事テーブル
export const posts = pgTable('posts', {
  id: serial('id').primaryKey(),
  title: text('title').notNull(),
  content: text('content'),
  authorId: integer('author_id').references(() => users.id),
  createdAt: timestamp('created_at').defaultNow(),
});

// 関係定義(一対多)
export const usersRelations = relations(users, ({ many }) => ({
  posts: many(posts),
}));

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

見てください、ただのTypeScriptコードです。Prismaのような特殊なスキーマファイルも、Client生成も不要です。

データベース接続の設定

db/index.ts を作成:

import { drizzle } from 'drizzle-orm/neon-http';
import { neon } from '@neondatabase/serverless';
import * as schema from './schema';

// 環境変数からデータベース接続を読み込む
const sql = neon(process.env.DATABASE_URL!);

// Drizzleインスタンスを作成
export const db = drizzle(sql, { schema });

これだけです。.env.local に以下を追加:

DATABASE_URL=postgres://user:[email protected]/dbname

Drizzle Kit(マイグレーションツール)の設定

drizzle.config.ts を作成:

import { defineConfig } from 'drizzle-kit';

export default defineConfig({
  schema: './db/schema.ts',
  out: './drizzle',
  dialect: 'postgresql',
  dbCredentials: {
    url: process.env.DATABASE_URL!,
  },
});

マイグレーションファイルを生成:

npx drizzle-kit generate

drizzle/ ディレクトリにSQLマイグレーションファイルが生成されます。内容を確認し、問題なければ実行します:

npx drizzle-kit push

これでデータベーステーブルが作成されました。

最終プロジェクト構造

my-drizzle-app/
├── app/                  # Next.js アプリ
│   ├── page.tsx
│   └── actions.ts        # Server Actions
├── db/
│   ├── schema.ts         # テーブル定義と関係
│   └── index.ts          # データベース接続
├── drizzle/
│   └── migrations/       # マイグレーションファイル(自動生成)
├── drizzle.config.ts     # Drizzle Kit 設定
├── .env.local            # 環境変数
└── package.json

設定プロセス全体で5分もかかりません。複雑なPrisma Schema構文も、長い prisma generate もなく、純粋なTypeScriptコードだけです。

Drizzle SQLライクAPI 実戦

設定が完了したので、Drizzleでのクエリの書き方を見てみましょう。最もよく使う操作と、Next.js Server Actionsでの実際の使い方を紹介します。

基礎CRUD操作

データ検索(SELECT)

import { db } from '@/db';
import { users, posts } from '@/db/schema';
import { eq, like, and, or, desc } from 'drizzle-orm';

// 全ユーザー検索
const allUsers = await db.select().from(users);

// 単一ユーザー検索
const user = await db.select().from(users).where(eq(users.id, 1));

// あいまい検索
const result = await db.select().from(users).where(like(users.name, '%John%'));

// 複合条件
const admins = await db
  .select()
  .from(users)
  .where(
    and(
      eq(users.role, 'admin'),
      gt(users.createdAt, new Date('2024-01-01'))
    )
  );

// ソートと制限
const latestPosts = await db
  .select()
  .from(posts)
  .orderBy(desc(posts.createdAt))
  .limit(10);

SQLのロジックそのままで、関数呼び出しに変わっただけですね。

データ挿入(INSERT)

// 単体挿入
await db.insert(users).values({
  name: 'John Doe',
  email: '[email protected]',
});

// 複数挿入
await db.insert(users).values([
  { name: 'Alice', email: '[email protected]' },
  { name: 'Bob', email: '[email protected]' },
]);

// 挿入したデータを返す
const [newUser] = await db
  .insert(users)
  .values({ name: 'Charlie', email: '[email protected]' })
  .returning();

console.log(newUser.id); // 自動生成されたID

データ更新(UPDATE)

// 単体更新
await db
  .update(users)
  .set({ name: 'Jane Doe' })
  .where(eq(users.id, 1));

// 一括更新
await db
  .update(posts)
  .set({ published: true })
  .where(eq(posts.authorId, 1));

// 更新後のデータを返す
const [updatedUser] = await db
  .update(users)
  .set({ name: 'Updated Name' })
  .where(eq(users.id, 1))
  .returning();

データ削除(DELETE)

// 単体削除
await db.delete(users).where(eq(users.id, 1));

// 一括削除
await db.delete(posts).where(eq(posts.published, false));

// 削除したデータを返す
const deleted = await db
  .delete(users)
  .where(eq(users.id, 1))
  .returning();

高度なクエリ

JOIN 操作

// LEFT JOIN:ユーザーとその記事を検索
const usersWithPosts = await db
  .select({
    userId: users.id,
    userName: users.name,
    postId: posts.id,
    postTitle: posts.title,
  })
  .from(users)
  .leftJoin(posts, eq(users.id, posts.authorId));

// INNER JOIN:記事のあるユーザーのみ検索
const activeAuthors = await db
  .select()
  .from(users)
  .innerJoin(posts, eq(users.id, posts.authorId));

サブクエリ

// 記事数が5以上のユーザーを検索
const sq = db
  .select({ authorId: posts.authorId, count: count() })
  .from(posts)
  .groupBy(posts.authorId)
  .having(gt(count(), 5))
  .as('sq');

const prolificAuthors = await db
  .select()
  .from(users)
  .innerJoin(sq, eq(users.id, sq.authorId));

集計関数

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

// ユーザー総数カウント
const [{ total }] = await db
  .select({ total: count() })
  .from(users);

// 作者ごとの記事数カウント
const postCounts = await db
  .select({
    authorId: posts.authorId,
    count: count(),
  })
  .from(posts)
  .groupBy(posts.authorId);

Next.js Server Actions での使用

app/actions.ts を作成:

'use server';

import { db } from '@/db';
import { users } from '@/db/schema';
import { eq } from 'drizzle-orm';
import { revalidatePath } from 'next/cache';

// ユーザー作成
export async function createUser(formData: FormData) {
  const name = formData.get('name') as string;
  const email = formData.get('email') as string;

  try {
    await db.insert(users).values({ name, email });
    revalidatePath('/users');
    return { success: true };
  } catch (error) {
    return { success: false, error: 'ユーザー作成失敗' };
  }
}

// 全ユーザー取得
export async function getUsers() {
  return await db.select().from(users);
}

// ユーザー削除
export async function deleteUser(id: number) {
  try {
    await db.delete(users).where(eq(users.id, id));
    revalidatePath('/users');
    return { success: true };
  } catch (error) {
    return { success: false, error: 'ユーザー削除失敗' };
  }
}

ページでの使用:

// app/users/page.tsx
import { getUsers } from '../actions';

export default async function UsersPage() {
  const users = await getUsers();

  return (
    <div>
      <h1>ユーザーリスト</h1>
      <ul>
        {users.map(user => (
          <li key={user.id}>
            {user.name} - {user.email}
          </li>
        ))}
      </ul>
    </div>
  );
}

TypeScript 型安全性

これこそがDrizzleの真骨頂——完全な型推論です。

// 戻り値の型を自動推論
const users = await db.select().from(users);
// 型:{ id: number; name: string; email: string; createdAt: Date }[]

// カスタム戻り値フィールド、型も自動推論
const result = await db
  .select({
    id: users.id,
    name: users.name,
  })
  .from(users);
// 型:{ id: number; name: string }[]

// エラーはコンパイル時に捕捉される
await db.select().from(users).where(eq(users.id, '1'));
// ❌ TypeScript エラー:型 'string' を型 'number' に割り当てることはできません

IntelliSenseは利用可能なすべてのフィールド、関数、条件演算子を提示してくれます。コードを書くのはゲーム感覚で、ドットを押せばすべてが出てきます。

APIを覚える必要も、ドキュメントを調べる必要もありません。TypeScriptコンパイラが最高のドキュメントです。

Drizzle vs Prisma 徹底比較

Drizzleの良い点ばかり話しましたが、客観的に全面比較してみましょう。自分のプロジェクトにどちらが適しているか判断するためです。

性能比較

~7.4kb
Drizzle Bundle
vs Prisma 14MB
~600ms
コールドスタート時間
vs Prisma 2.5s
0
ランタイム依存
純粋なTypeScript
数据来源: 本番環境監視データ
比較項目DrizzlePrisma v5Prisma v7備考
Bundleサイズ~7.4kb~14MB~1MBDrizzleが最軽量
コールドスタート~600ms~2.5s~1.5sサーバーレス環境
ランタイム依存0個Rustバイナリ0個DrizzleとPrisma v7は依存なし
メモリ使用量~5MB~80MB~30MB(推定)実際の実行時メモリ
型チェック速度高速普通普通Drizzleの推論がよりシンプル

リアルな事例データ

私たちのチームのプロジェクトで、Prisma v5からDrizzleへ移行した後:

  • 初回リクエスト時間:3s → 700ms(76%高速化)
  • 本番バンドルサイズ:18MB → 4MB(78%削減)
  • Lambdaコールドスタート:2.4s → 650ms(73%高速化)

これらの数字はテスト環境のものではなく、実際の本番環境のモニタリングデータです。

開発体験比較

Schema定義方式

// Drizzle(TypeScriptコード)
export const users = pgTable('users', {
  id: serial('id').primaryKey(),
  name: text('name').notNull(),
});

// Prisma(専用DSL)
model User {
  id   Int    @id @default(autoincrement())
  name String
}

Drizzleの利点:TypeScriptそのものなのでIDEサポートが良く、TypeScriptの全機能(条件付き型、ジェネリクス等)が使える

Prismaの利点:Prisma Schemaはより簡潔で、可読性が高い

クエリAPIスタイル

// Drizzle(SQLライク)
await db
  .select()
  .from(users)
  .leftJoin(posts, eq(users.id, posts.authorId))
  .where(gt(posts.views, 1000));

// Prisma(チェーン式API)
await prisma.user.findMany({
  include: {
    posts: {
      where: { views: { gt: 1000 } },
    },
  },
});

Drizzleの利点:SQLに近く、複雑なクエリを表現しやすい

Prismaの利点:SQLに不慣れな開発者に優しく、ネストしたクエリの意味が明確

機能比較

Drizzleの強み

  1. 究極の軽量化:コア7.4kb、バンドルサイズに敏感なプロジェクトに必須
  2. サーバーレスネイティブ:コールドスタートが速く、Edge環境に適応
  3. SQL制御力が強い:複雑なクエリや性能最適化が柔軟
  4. Client生成不要:スキーマを変えたらすぐ使える、prisma generate は不要
  5. Tree-shakable:使用したコードのみバンドルされる

Prismaの強み

  1. エコシステムが成熟:2021年リリース、コミュニティが大きく、チュートリアルが多く、ハマりどころが少ない
  2. 開発ツールが充実:Prisma Studio(DB管理)、Prisma Migrate(自動移行)、Prisma Pulse(リアルタイム購読)
  3. 関係クエリがスマート:N+1問題を自動処理、ネストクエリが直感的
  4. 初心者に優しい:SQLを深く理解する必要がなく、DSLの学習曲線が緩やか
  5. エラー提示が詳細:ランタイムエラー情報が親切

選定アドバイス

Drizzleを選ぶべきシーン

✅ サーバーレス/エッジ環境(Vercel、Cloudflare Workers、Deno Deploy)
✅ 性能重視のシーン(リアルタイムアプリ、金融システム、高同時API)
✅ バンドルサイズ制限あり(< 1MB制限環境)
✅ チームのSQL基礎があり、クエリを直接制御したい
✅ 複雑なSQL最適化が必要なプロジェクト

Prismaを選ぶべきシーン

✅ チームがSQLに不慣れで、急速に立ち上げたい
✅ 開発体験とツールチェーンの完全性を重視
✅ 可視化データベース管理が必要(Prisma Studio)
✅ 複雑なリレーションクエリとデータモデリング
✅ プロジェクトがサーバーレス環境でなく、バンドルサイズに敏感でない

私の個人的アドバイス

もし新規プロジェクトをやるなら、私はこう選びます:

  • 個人/スタートアップ:Drizzle(性能が良く、コストが低く、極限まで最適化できる)
  • 企業/チーム開発:チーム背景による。SQLが強ければDrizzle、弱ければPrisma
  • サーバーレスファースト:迷わずDrizzle
  • 従来型サーバーデプロイ:どちらでもOK、Prismaのツールチェーンは魅力的

ハイブリッド案もあります:コアの性能重視モジュールはDrizzle、管理画面はPrisma。両者は共存可能で衝突しません。

移行ガイドとベストプラクティス

PrismaからDrizzleへの移行

すでにPrismaプロジェクトがあり、Drizzleを試したい場合、段階的に移行できます。

ステップ1:Schema変換

// Prisma Schema
model User {
  id        Int      @id @default(autoincrement())
  name      String
  email     String   @unique
  posts     Post[]
  createdAt DateTime @default(now())
}

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

ステップ2:クエリ書き換え

// Prisma クエリ
const user = await prisma.user.findUnique({
  where: { id: 1 },
  include: { posts: true },
});

// Drizzle クエリ
const [user] = await db
  .select()
  .from(users)
  .where(eq(users.id, 1))
  .leftJoin(posts, eq(users.id, posts.authorId));

ステップ3:段階的置換

PrismaとDrizzleを共存させられます:

// 性能重視のクエリはDrizzle
import { db } from '@/db/drizzle';
const latestPosts = await db
  .select()
  .from(posts)
  .orderBy(desc(posts.createdAt))
  .limit(50);

// 複雑なリレーションクエリは暫定的にPrisma
import { prisma } from '@/db/prisma';
const userWithRelations = await prisma.user.findUnique({
  where: { id: 1 },
  include: {
    posts: { include: { comments: { include: { author: true } } } },
  },
});

少しずつ置き換え、リスクを下げましょう。

Drizzle ベストプラクティス

1. コネクションプール管理

サーバーレス環境ではコネクションプールに特に注意:

import { drizzle } from 'drizzle-orm/neon-http';
import { neon, neonConfig } from '@neondatabase/serverless';

// プール設定
neonConfig.fetchConnectionCache = true;

const sql = neon(process.env.DATABASE_URL!);
export const db = drizzle(sql, { schema });

2. プリコンパイルクエリ

高頻度のクエリはプリコンパイルして性能アップ:

import { db } from '@/db';
import { users } from '@/db/schema';
import { eq } from 'drizzle-orm';

// クエリのプリコンパイル
const getUserById = db
  .select()
  .from(users)
  .where(eq(users.id, placeholder('id')))
  .prepare('get_user_by_id');

// 使用
const user = await getUserById.execute({ id: 1 });

3. トランザクション処理

await db.transaction(async (tx) => {
  // ユーザー作成
  const [user] = await tx
    .insert(users)
    .values({ name: 'John', email: '[email protected]' })
    .returning();

  // 関連する記事を作成
  await tx.insert(posts).values({
    title: 'First Post',
    authorId: user.id,
  });

  // いずれかが失敗すれば、トランザクション全体がロールバック
});

4. 型の再利用

推論された型をエクスポートしてフロントエンドで使用:

// db/schema.ts
export const users = pgTable('users', {
  id: serial('id').primaryKey(),
  name: text('name').notNull(),
  email: text('email').notNull(),
});

// 推論型をエクスポート
export type User = typeof users.$inferSelect;
export type NewUser = typeof users.$inferInsert;

// フロントエンドで使用
import type { User } from '@/db/schema';

function UserCard({ user }: { user: User }) {
  return <div>{user.name}</div>;
}

5. エラーハンドリング

import { db } from '@/db';
import { users } from '@/db/schema';

try {
  await db.insert(users).values({
    name: 'John',
    email: '[email protected]',
  });
} catch (error) {
  // PostgreSQL エラーコード
  if (error.code === '23505') {
    console.error('メールアドレスが既に存在します');
  } else {
    console.error('データベースエラー', error);
  }
}

結論

長くなりましたが、まとめましょう。

Drizzle ORMはTypeScript ORM界の一服の清涼剤です——SQLを隠蔽せず、SQLを受け入れます。7.4kbのコア、ランタイム依存ゼロ、ネイティブSQLに近い性能で、**「軽量化は妥協ではなく、より良い選択である」**ことを証明しました。

Prismaの完全な代替品ではありません。Prismaには成熟したエコシステム、完璧なツールチェーン、初心者に優しい体験があります。しかし、もしあなたのプロジェクトが:

  • サーバーレス環境で動く
  • 性能とバンドルサイズに敏感
  • 複雑なSQL制御が必要
  • チームにSQLの基礎がある

ならば、Drizzleはより適切な選択かもしれません。

私たちのチームの実際のデータがすべてを物語っています:PrismaからDrizzleへ移行後、コールドスタートは73%高速化、バンドルは78%削減、初回リクエストは3秒から700msに短縮。これは微調整ではなく、質的な飛躍です。

もしPrismaの遅いコールドスタートや巨大なバンドルに頭を悩ませているなら、Drizzleを試してみてください。学習コストは低いです——SQLがわかれば、10分で使いこなせます。

最後に、役立つリンクを貼っておきます:

試してみてください。私と同じように、もう戻れなくなるかもしれません。

Next.js + Drizzle ORM 完全設定・移行ガイド

Drizzle ORMのゼロからの設定、環境準備、Schema定義、DB接続、クエリ使用、そしてPrismaからの移行までの完全ステップ

⏱️ Estimated time: 2 hr

  1. 1

    Step1: ステップ1:環境準備と依存インストール

    Next.jsプロジェクト作成:
    ```bash
    npx create-next-app@latest my-drizzle-app
    cd my-drizzle-app
    ```

    Drizzle関連依存のインストール:
    ```bash
    npm install drizzle-orm drizzle-kit
    npm install @neondatabase/serverless # Neonデータベースなら
    # または
    npm install postgres # 従来のPostgreSQLなら
    ```

    Neon推奨(serverless PostgreSQL):
    • 無料アカウント登録:https://neon.tech
    • データベース作成、接続文字列取得
    • Drizzleと相性抜群、Edge環境対応

    .env.local に追加:
    ```
    DATABASE_URL=postgres://user:[email protected]/dbname
    ```
  2. 2

    Step2: ステップ2:データベースSchema定義

    db/schema.ts を作成し、テーブル構造を定義:

    ```typescript
    import { pgTable, serial, text, timestamp, integer } from 'drizzle-orm/pg-core';
    import { relations } from 'drizzle-orm';

    // ユーザーテーブル
    export const users = pgTable('users', {
    id: serial('id').primaryKey(),
    name: text('name').notNull(),
    email: text('email').notNull().unique(),
    createdAt: timestamp('created_at').defaultNow(),
    });

    // 記事テーブル
    export const posts = pgTable('posts', {
    id: serial('id').primaryKey(),
    title: text('title').notNull(),
    content: text('content'),
    authorId: integer('author_id').references(() => users.id),
    createdAt: timestamp('created_at').defaultNow(),
    });

    // 関係定義(一対多)
    export const usersRelations = relations(users, ({ many }) => ({
    posts: many(posts),
    }));

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

    ポイント:
    • TypeScriptコードそのもの、Prismaのような特殊ファイルなし
    • Client生成不要、型は自動推論
    • TypeScriptの全機能利用可能
  3. 3

    Step3: ステップ3:DB接続とDrizzle Kit設定

    db/index.ts 作成:

    ```typescript
    import { drizzle } from 'drizzle-orm/neon-http';
    import { neon } from '@neondatabase/serverless';
    import * as schema from './schema';

    const sql = neon(process.env.DATABASE_URL!);
    export const db = drizzle(sql, { schema });
    ```

    drizzle.config.ts 作成:

    ```typescript
    import { defineConfig } from 'drizzle-kit';

    export default defineConfig({
    schema: './db/schema.ts',
    out: './drizzle',
    dialect: 'postgresql',
    dbCredentials: {
    url: process.env.DATABASE_URL!,
    },
    });
    ```

    マイグレーション実行:
    ```bash
    npx drizzle-kit generate
    npx drizzle-kit push
    ```

    設定完了まで5分以内。
  4. 4

    Step4: ステップ4:Drizzle SQLライクAPIでのクエリ

    基礎CRUD:

    検索(SELECT):
    ```typescript
    const user = await db.select().from(users).where(eq(users.id, 1));
    ```

    挿入(INSERT):
    ```typescript
    await db.insert(users).values({ name: 'John', email: '[email protected]' });
    ```

    高度なクエリ(JOIN):
    ```typescript
    const usersWithPosts = await db
    .select()
    .from(users)
    .leftJoin(posts, eq(users.id, posts.authorId));
    ```

    ポイント:
    • SQL構文に近い構造
    • 完全な型推論とIntelliSense
    • コンパイル時エラー検出
  5. 5

    Step5: ステップ5:Next.js Server Actionsでの使用

    app/actions.ts:

    ```typescript
    'use server';
    import { db } from '@/db';
    import { users } from '@/db/schema';

    export async function createUser(formData: FormData) {
    // ...
    await db.insert(users).values({ name, email });
    revalidatePath('/users');
    }
    ```
  6. 6

    Step6: ステップ6:Prismaからの移行(任意)

    段階的移行が可能:

    1. Schema変換:Prisma model → Drizzle pgTable
    2. クエリ書き換え:prisma.findMany → db.select
    3. 共存運用:性能重要な箇所はDrizzle、複雑な箇所はPrismaと使い分け、徐々に置換

FAQ

Drizzle ORMとPrismaの違いは?いつDrizzleを選ぶべき?
核心的な違い:

性能:
• Drizzle:7.4kb、コールドスタート600ms、依存なし
• Prisma:v5は14MB/2.5s、v7は1MB/1.5s

開発体験:
• Drizzle:SQLライク、Client生成不要
• Prisma:専用DSL、Client生成必要

Drizzleを選ぶべき時:
✅ サーバーレス/エッジ環境
✅ 性能・バンドルサイズ重視
✅ SQL制御が必要
✅ チームにSQL基礎がある

Prismaを選ぶべき時:
✅ SQL不慣れなチーム
✅ ツールチェーン重視(Studio等)
✅ サーバーレスでない
Drizzle ORMのSQLライクAPIとは具体的に?
TypeScriptコードをSQL構文に近づけた設計です。

SQL:
SELECT * FROM posts WHERE id = 10

Drizzle:
await db.select().from(posts).where(eq(posts.id, 10))

特徴:
• SQLを知っていれば学習コストほぼゼロ
• 完全な型推論と自動補完
• Prismaの独自DSLを覚える必要がない
Drizzle ORMのサーバーレスでの性能優位性は?
Prisma v5から移行した実測データ:
• 初回リクエスト:3s → 700ms(76%高速化)
• バンドルサイズ:18MB → 4MB(78%削減)
• Lambdaコールドスタート:2.4s → 650ms(73%高速化)

理由:
• 軽量コア(7.4kb)
• 起動時の巨大なスキーマ解析がない
• メモリ使用量が少ない
Prismaからの移行は複雑ですか?
いいえ、段階的移行が可能です。
1. Schema変換(Model → pgTable)
2. クエリ書き換え(findMany → select)
3. 漸進的置換(共存させて徐々に移行)
というステップで、リスクを抑えて移行できます。
Drizzle ORMはどのDBと環境をサポートしていますか?
DB:
• PostgreSQL(Neon推奨)
• MySQL
• SQLite(Turso等)
• SQL Server

環境:
• Vercel Edge Functions
• Cloudflare Workers
• AWS Lambda
• Deno Deploy
• Bun
すべてネイティブサポートです。
型安全性はどうですか?
Prismaよりシンプルで強力です。
Client生成なしでTypeScriptが直接型を推論します。
• 戻り値の型も自動推論
• IntelliSenseで全フィールド補完
• コンパイル時にエラー検出
大規模プロジェクトでもコンパイル速度を落としません。
学習コストは?
SQLがわかれば10分で使えます。
新しいDSLを覚える必要がないため、Prismaより学習コストは低いです(SQL基礎がある場合)。

9 min read · 公開日: 2025年12月20日 · 更新日: 2026年1月22日

コメント

GitHubアカウントでログインしてコメントできます

関連記事