※本記事には広告(A8・楽天アフィリエイト)リンクが含まれる場合があります。

「PrismaのRustバイナリが重くてCloudflare Workersに載らない」「型が合わなくてイライラする」「スキーマファイルとTypeScriptを行き来するのが面倒」——TypeScriptでバックエンドを書いている個人開発者なら一度は感じたことがある不満です。

そんな悩みを解決するのが Drizzle ORM です。2022年登場のTypeScript-firstな軽量ORMで、2026年現在は特にエッジ・サーバーレス環境で急速に普及しています。バンドルサイズはわずか280KB(Prismaのクライアント+エンジンが約55MB)、コード生成ステップ不要、そしてSQLがそのまま書けるシンプルさが最大の特徴です。

この記事では、Drizzle ORMの基本概念からCloudflare D1・Supabaseとの実践的な組み合わせ、マイグレーション管理まで、実例コードを交えて徹底解説します。

Drizzle ORMとは?なぜ2026年に注目されるのか

Drizzle ORMは「SQLをそのまま書ける型安全なクエリビルダー」です。公式のキャッチコピーは "If you know SQL, you know Drizzle." ——SQLを知っていれば学習コストがほぼゼロという設計思想です。

Drizzle ORMの主な特徴

  • TypeScript-first:スキーマもクエリも純粋なTypeScript。コード生成ステップが不要
  • 超軽量:バンドルサイズ約280KB。Cloudflare WorkersやDeno Deployでも問題なく動く
  • SQLライクなクエリ.select().from().where() という構文でSQLのメンタルモデルをそのまま使える
  • 完全な型推論:スキーマ定義からクエリ結果の型まで、すべて自動推論。any を一切使わない
  • マルチDB対応:PostgreSQL・MySQL・SQLite(Cloudflare D1含む)・Tursoなど幅広く対応
  • Drizzle Studio:ブラウザベースのGUIでDBを直接操作できる組み込みツール

PrismaとDrizzleの使い分け

観点 Prisma Drizzle
スキーマ .prisma(独自DSL) TypeScript
コード生成 必要(prisma generate) 不要
バンドルサイズ 〜55MB(Rustエンジン含む) 〜280KB
エッジ対応 限定的(Accelerate経由) ✅ ネイティブ対応
クエリスタイル オブジェクト指向API SQLライク
学習コスト 中(独自のAPIを覚える) 低(SQLを知れば即使える)
向いている場面 フルスタックアプリ、チーム開発 エッジ・サーバーレス、個人開発

Cloudflare WorkersやDeno Deployでバックエンドを動かしたい個人開発者には、Drizzle一択といっても過言ではありません。

インストールとプロジェクトセットアップ

インストール

# コアライブラリ + drizzle-kit(マイグレーション管理CLIツール)
npm install drizzle-orm
npm install -D drizzle-kit

# 使うDBドライバに応じて追加
# Cloudflare D1の場合(追加インストール不要、@cloudflare/workers-typesのみ)
npm install -D @cloudflare/workers-types

# PostgreSQL(Supabaseなど)の場合
npm install postgres

# SQLite(ローカル開発・Bun)の場合
npm install better-sqlite3
npm install -D @types/better-sqlite3

drizzle.config.tsの設定

// drizzle.config.ts
import { defineConfig } from 'drizzle-kit';

export default defineConfig({
  schema: './src/schema.ts',   // スキーマ定義ファイル
  out: './drizzle',            // マイグレーションファイルの出力先
  dialect: 'sqlite',           // 使用するDB('postgresql' | 'mysql' | 'sqlite')
  // Cloudflare D1の場合は dialect: 'sqlite' を使う
});

広告・楽天アフィリエイト

開発者向けガジェット

PC周辺機器を楽天市場でチェック。

楽天ポイント還元

楽天市場を見る →

広告・楽天アフィリエイト

メカニカルキーボード

打鍵感で選ぶ開発者向けキーボード。

楽天ポイント還元

キーボードを楽天で見る →

スキーマ定義:TypeScriptがそのままスキーマになる

Drizzleの最大の特徴は、スキーマ定義が純粋なTypeScriptファイルであること。.prisma のような独自言語を覚える必要がなく、エディタの補完・型チェックがそのまま効きます。

SQLite(Cloudflare D1)用スキーマ

// src/schema.ts
import { sqliteTable, text, integer, real } from 'drizzle-orm/sqlite-core';
import { relations } from 'drizzle-orm';

// usersテーブル
export const users = sqliteTable('users', {
  id: integer('id').primaryKey({ autoIncrement: true }),
  email: text('email').notNull().unique(),
  name: text('name').notNull(),
  plan: text('plan', { enum: ['free', 'pro'] }).notNull().default('free'),
  createdAt: text('created_at').notNull().default(new Date().toISOString()),
});

// postsテーブル
export const posts = sqliteTable('posts', {
  id: integer('id').primaryKey({ autoIncrement: true }),
  userId: integer('user_id').notNull().references(() => users.id),
  title: text('title').notNull(),
  content: text('content'),
  published: integer('published', { mode: 'boolean' }).notNull().default(false),
  createdAt: text('created_at').notNull().default(new Date().toISOString()),
});

// リレーション定義
export const usersRelations = relations(users, ({ many }) => ({
  posts: many(posts),
}));

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

PostgreSQL(Supabase)用スキーマ

// src/schema.ts(PostgreSQL版)
import { pgTable, serial, varchar, text, boolean, timestamp } from 'drizzle-orm/pg-core';

export const users = pgTable('users', {
  id: serial('id').primaryKey(),
  email: varchar('email', { length: 256 }).notNull().unique(),
  name: varchar('name', { length: 100 }).notNull(),
  plan: varchar('plan', { length: 10, enum: ['free', 'pro'] }).notNull().default('free'),
  createdAt: timestamp('created_at').defaultNow(),
});

export const posts = pgTable('posts', {
  id: serial('id').primaryKey(),
  userId: serial('user_id').references(() => users.id),
  title: varchar('title', { length: 200 }).notNull(),
  content: text('content'),
  published: boolean('published').notNull().default(false),
  createdAt: timestamp('created_at').defaultNow(),
});
ポイント: スキーマ定義から型が自動的に推論されます。typeof users.$inferSelect で SELECT結果の型、typeof users.$inferInsert でINSERT時の型を取得でき、別途型定義を書く必要がありません。

基本的なCRUD操作

DBクライアントの初期化(Cloudflare D1の場合)

// src/db.ts
import { drizzle } from 'drizzle-orm/d1';
import * as schema from './schema';

// Workers環境ではenv.DBを渡す
export function createDb(d1: D1Database) {
  return drizzle(d1, { schema });
}

SELECT(データ取得)

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

// 全ユーザーを取得
const allUsers = await db.select().from(users).all();

// 条件でフィルタ(SQLのWHEREそのまま)
const proUsers = await db
  .select()
  .from(users)
  .where(eq(users.plan, 'pro'))
  .orderBy(desc(users.createdAt))
  .limit(10)
  .all();

// JOINして関連データを一緒に取得
const usersWithPosts = await db
  .select({
    userId: users.id,
    userName: users.name,
    postTitle: posts.title,
    published: posts.published,
  })
  .from(users)
  .leftJoin(posts, eq(users.id, posts.userId))
  .where(eq(posts.published, true))
  .all();

// リレーション経由でネスト取得(Prismaのincludeに近い書き方)
const userWithPosts = await db.query.users.findFirst({
  where: eq(users.id, 1),
  with: {
    posts: {
      where: eq(posts.published, true),
      orderBy: desc(posts.createdAt),
    },
  },
});

INSERT(データ作成)

// 単一レコードの挿入
const newUser = await db
  .insert(users)
  .values({ email: 'user@example.com', name: '山田太郎' })
  .returning()  // SQLite(D1)では.run()を使い、RETURNINGは個別クエリで
  .get();

// Cloudflare D1(SQLite)の場合、RETURNINGが使えるバージョン確認が必要
// 安全な書き方:
await db.insert(users).values({ email: 'user@example.com', name: '山田太郎' }).run();
const inserted = await db.select().from(users)
  .where(eq(users.email, 'user@example.com'))
  .get();

// バルクインサート
await db.insert(posts).values([
  { userId: 1, title: '記事1', content: '本文1', published: true },
  { userId: 1, title: '記事2', content: '本文2', published: false },
]).run();

UPDATE / DELETE

// プランをproに更新
await db
  .update(users)
  .set({ plan: 'pro' })
  .where(eq(users.id, userId))
  .run();

// 複数条件でアップデート
await db
  .update(posts)
  .set({ published: true })
  .where(and(eq(posts.userId, userId), eq(posts.published, false)))
  .run();

// 削除
await db.delete(posts).where(eq(posts.id, postId)).run();

Cloudflare D1との連携:エッジDBで超低コスト個人開発

Cloudflare D1はCloudflareのエッジSQLiteデータベースです。Drizzleとの組み合わせは2026年の個人開発スタックとして非常に人気があります。Honoと組み合わせれば、型安全なAPIが数十行で完成します。

// src/index.ts(Hono + Drizzle + D1の組み合わせ)
import { Hono } from 'hono';
import { drizzle } from 'drizzle-orm/d1';
import { zValidator } from '@hono/zod-validator';
import { z } from 'zod';
import { users, posts } from './schema';
import { eq, desc } from 'drizzle-orm';

type Bindings = { DB: D1Database };

const app = new Hono<{ Bindings: Bindings }>();

// ユーザー一覧API
app.get('/api/users', async (c) => {
  const db = drizzle(c.env.DB);
  const result = await db.select().from(users).orderBy(desc(users.createdAt)).all();
  return c.json(result);
});

// ユーザー作成API(バリデーション付き)
const createUserSchema = z.object({
  email: z.string().email(),
  name: z.string().min(1).max(100),
});

app.post('/api/users', zValidator('json', createUserSchema), async (c) => {
  const db = drizzle(c.env.DB);
  const { email, name } = c.req.valid('json');
  try {
    await db.insert(users).values({ email, name }).run();
    const user = await db.select().from(users).where(eq(users.email, email)).get();
    return c.json(user, 201);
  } catch (e) {
    if (e instanceof Error && e.message.includes('UNIQUE')) {
      return c.json({ error: 'このメールアドレスはすでに使用されています' }, 409);
    }
    return c.json({ error: 'Internal server error' }, 500);
  }
});

// ユーザーの投稿一覧API
app.get('/api/users/:id/posts', async (c) => {
  const db = drizzle(c.env.DB);
  const userId = Number(c.req.param('id'));
  const result = await db
    .select()
    .from(posts)
    .where(eq(posts.userId, userId))
    .orderBy(desc(posts.createdAt))
    .all();
  return c.json(result);
});

export default app;

このスタックならCold Startが5ms以下、グローバルにデプロイでき、かつ月$0から運用できま す。当サイトで提供している PagePulse(Web監視ツール)StatusCraft(ステータスページ) なども、同様のCloudflareエッジスタックで構築しています。

広告・楽天アフィリエイト

PCモニター

デュアルディスプレイや高解像度モデルをチェック。

楽天ポイント還元

モニターを楽天で見る →

広告・楽天アフィリエイト

プログラミング技術書

実践寄りの技術書・参考書を探す。

楽天ポイント還元

技術書を楽天で見る →

Supabase(PostgreSQL)との連携

SupabaseのPostgreSQLをDrizzleから操作する場合は、postgres ドライバを使います。

// src/db.ts(Supabase + Drizzle)
import { drizzle } from 'drizzle-orm/postgres-js';
import postgres from 'postgres';
import * as schema from './schema';

const connectionString = process.env.DATABASE_URL!;
// Supabaseは接続プール対応のため max: 1 推奨(サーバーレス環境)
const client = postgres(connectionString, { max: 1 });
export const db = drizzle(client, { schema });

// 使用例(Supabaseのrow level securityと組み合わせる場合はset_config必要)
const userPosts = await db
  .select()
  .from(posts)
  .where(and(
    eq(posts.userId, userId),
    eq(posts.published, true)
  ))
  .orderBy(desc(posts.createdAt))
  .limit(20);

マイグレーション管理:drizzle-kit

Drizzleのマイグレーションは drizzle-kit で管理します。Prisma Migrateのようにシャドウデータベースが不要で、スキーマの差分から自動でSQLファイルを生成します。

基本的なマイグレーションワークフロー

# スキーマの差分からマイグレーションファイルを生成
npx drizzle-kit generate

# ローカルDBにマイグレーションを適用
npx drizzle-kit migrate

# Cloudflare D1の場合は wrangler 経由で適用
npx wrangler d1 execute MY_DB --remote --file=drizzle/0000_init.sql

# スキーマをDBに直接プッシュ(開発環境向け、マイグレーションファイルなし)
npx drizzle-kit push

Drizzle Studio:ブラウザでDBを操作

# ブラウザGUIを起動(https://local.drizzle.studio で開く)
npx drizzle-kit studio

Drizzle Studioを使えば、開発中にDBの内容をGUIで確認・編集できます。TablePlusやDBeaverのような外部ツールを別途インストールする必要がなく、個人開発のDXが大幅に向上します。

本番DBへの接続: drizzle.config.ts に接続情報を記述すれば本番DBにもStudioから接続できます。ただし本番データの誤操作に注意してください。

型安全の恩恵:開発体験が格段に向上する

Drizzleの最大の強みは、スキーマ定義からクエリ結果の型まですべてが自動推論される点です。実際のコードで見てみましょう。

// スキーマ定義から型を自動取得
type User = typeof users.$inferSelect;
// → { id: number; email: string; name: string; plan: 'free' | 'pro'; createdAt: string }

type NewUser = typeof users.$inferInsert;
// → { email: string; name: string; id?: number; plan?: 'free' | 'pro'; createdAt?: string }

// クエリ結果も完全に型推論される
const result = await db
  .select({ id: users.id, email: users.email })
  .from(users)
  .get();
// result の型: { id: number; email: string } | undefined
// → res.id は number として補完される。any は一切ない

// 存在しないカラムを指定するとコンパイルエラー
const bad = await db
  .select({ nonExistent: users.nonExistentColumn }) // ❌ TypeScriptエラー
  .from(users)
  .all();

この型安全性のおかげで、ランタイムエラーの大部分をコンパイル時に検出できます。特に個人開発でテストが少ない場合に、この恩恵は非常に大きいです。AI コーディングツールとの組み合わせでも、型情報が正確なため補完精度が上がります。

よくあるハマりポイントと対処法

1. Cloudflare D1でRETURNINGが動かない

D1のSQLiteバージョンによってはRETURNINGが使えない場合があります。その場合は INSERTSELECT の2段階で対応しましょう。

// ❌ D1では動かない場合がある
const result = await db.insert(users).values(data).returning().get();

// ✅ 安全な代替パターン
await db.insert(users).values(data).run();
const inserted = await db.select().from(users).where(eq(users.email, data.email)).get();

2. リレーションクエリには schema を渡す必要がある

db.query.users.findMany({ with: { posts: true } }) のようなリレーション取得を使うには、drizzle(client, { schema }) でschemaを渡す必要があります。渡し忘れると実行時エラーになります。

3. 日付カラムはSQLiteでは文字列

SQLite(D1)では日付型がありません。text() で保存し、アプリ層で new Date() に変換するパターンが一般的です。PostgreSQLの timestamp() とは挙動が異なるので注意してください。

4. drizzle-kitのバージョン管理

drizzle-ormdrizzle-kit のバージョンは必ず揃えてください。ミスマッチがあるとマイグレーション生成が失敗します。

# バージョン確認
npx drizzle-kit --version
# drizzle-orm と drizzle-kit は同じメジャーバージョンを使う

個人開発での活用パターン:推奨スタック

2026年の個人開発に最適なDrizzle活用スタックをまとめます。

用途 推奨スタック 月額コスト目安
エッジAPI + 軽量DB Hono + Drizzle + Cloudflare D1 $0〜
フルスタックSaaS Next.js + Drizzle + Supabase $0〜(Supabase無料枠)
高速API + ローカルDB Hono + Drizzle + Turso(libSQL) $0〜
型安全な管理画面 Remix + Drizzle + PostgreSQL $5〜(Render.com等)

特に Hono + Drizzle + Cloudflare D1 の組み合わせは、Cold Startが5ms以下でグローバルに展開でき、かつ無料枠が非常に広いため、個人開発の最初のスタックとして非常に優秀です。

広告・楽天アフィリエイト

ガジェット・オーディオ

イヤホン・便利ガジェットをポイント還元で。

楽天ポイント還元

ガジェットを楽天で見る →

まとめ:2026年の個人開発ORMはDrizzleで決まり

Drizzle ORMは以下の理由から、2026年の個人開発TypeScriptスタックにおいてベストな選択肢のひとつです。

  • 学習コストが低い:SQLを知っていれば即使える。独自DSL不要
  • 超軽量・エッジ対応:280KB。Cloudflare WorkersやDeno Deployで問題なく動く
  • 完全な型安全:スキーマからクエリ結果まですべて型推論。ランタイムエラーを激減
  • コード生成不要prisma generate のような手順なし。保存即反映
  • Drizzle Studio:組み込みGUIで開発中のDB確認が簡単
  • ⚠️ エコシステムはPrismaより小さい:サードパーティプラグインの数はまだ少ない
  • ⚠️ 複雑なリレーション操作:Prismaの方がネストされたmutation(createMany with connect等)は書きやすい場合がある

「PrismaのRustエンジンが重くて困っている」「エッジ環境でORMを使いたい」「SQLを書くのが好きだけど型安全にしたい」——そんな方は、ぜひDrizzle ORMを試してみてください。

広告・楽天アフィリエイト

USBハブ・周辺機器

ドッキング・ケーブル類をまとめて比較。

楽天ポイント還元

周辺機器を楽天で見る →