Drizzle ORM × Cloudflare D1 — エッジで動く型安全なSQLiteの現在地

型安全なTypeScript ORMとして急速に普及するDrizzle ORMと、CloudflareのサーバーレスSQLiteデータベースD1の組み合わせを徹底解説。セットアップからマイグレーション、ローカル開発の落とし穴まで実践的にまとめる。

·
  • Drizzle
  • Cloudflare D1
  • TypeScript
  • ORM
  • Cloudflare Workers
  • SQLite
Drizzle ORMとCloudflare D1のロゴが並ぶ、エッジデータベースのイラスト

TypeScriptでWebアプリを書いていると、データベースまわりの選択で必ず直面する問いがある。「Prismaを使うか、それとも別の手段があるか」。その問いに対して2022年ごろから存在感を増してきたのが Drizzle ORM だ。そしてCloudflare Workersエコシステムで作業する開発者にとって、セットでついてくる選択肢が Cloudflare D1 である。

ふたつを組み合わせると何が得られるのか。セットアップはどう進めるのか。ローカル開発にはどんな落とし穴があるのか。この記事では、2026年現在の実情を整理する。

Drizzle ORMとは何か

Drizzle ORMを一言で表すなら「SQLを知っていればすぐ分かるTypeScript ORM」だ。公式のキャッチフレーズ “If you know SQL, you know Drizzle.” はそのままプロダクトの設計思想を表している。

具体的な特徴をまとめると、まずバンドルサイズが 約7.4KB(minified+gzipped)、依存ゼロ という軽量さが際立つ。PrismaがRust製バイナリを抱えてコールドスタートに時間がかかるのと対照的に、DrizzleはピュアJavaScriptで動く。この違いがサーバーレス・エッジ環境では決定的になる。

スキーマ定義はTypeScriptファイルで直接書く。専用のDSL(Prisma Schema Languageのような独立した言語)は存在しない。

import { int, text, sqliteTable } from "drizzle-orm/sqlite-core";

export const users = sqliteTable("users", {
  id: int().primaryKey({ autoIncrement: true }),
  name: text().notNull(),
  email: text().notNull().unique(),
  createdAt: int({ mode: "timestamp" }).notNull(),
});

クエリはSQLの構造に沿ったメソッドチェーンで書けて、戻り値の型はTypeScriptが自動推論してくれる。prisma generate のようなコード生成ステップは不要で、スキーマを変更したら即座に型チェックが走る。

週間npmダウンロード数はすでに 約90万回 に達しており、サーバーレス系プロジェクトではデファクトに近い存在になりつつある。

Drizzle Kitという相棒

ORMとセットで使う drizzle-kit がマイグレーション管理を担う。スキーマの変更を検出してSQLマイグレーションファイルを自動生成する generate コマンド、それを適用する migrate コマンド、開発中にスキーマを直接プッシュする push コマンドが用意されている。

さらに Drizzle Studio というGUIのデータブラウザも付属しており、ローカルとリモート双方のデータベースを視覚的に操作できる。

Cloudflare D1とは何か

D1はCloudflareが提供するマネージドのサーバーレスデータベースで、SQLiteのSQL方言を採用している。Cloudflare WorkersおよびCloudflare Pages Functionsから直接バインディングでアクセスでき、接続URLもコネクションプールも不要という独特のアーキテクチャが特徴だ。

D1はすでに GA(一般提供) となっており、本番利用が可能な状態にある。フリープランとWorkers有料プランの主な違いは次のとおりだ。

項目FreeプランWorkers有料プラン ($5/月〜)
データベース数10個まで50,000個まで
1DBあたりのストレージ500MB10GB
課金モデル1日あたりの上限ありrows_read / rows_writtenで従量課金

ストレージの転送(egress)には課金されない点も見逃せない。

D1の設計思想:小さいDBをたくさん作る

D1の上限が10GBである理由は制約ではなく設計思想にある。大きな1つのDBではなく、ユーザーごと・テナントごとに小さなDBを数万・数十万と水平にスケールアウトするモデルを想定している。Cloudflare Durable Objectsに基づく実装がこの思想を支えている。

また Time Travel という機能があり、過去30日以内の任意の時点にデータベースを復元できる。データの誤削除や意図しないマイグレーションへの対処として実用的な保険になる。

Drizzle × D1 のセットアップ

実際の手順を追う。前提としてCloudflare Workersのプロジェクトが存在していること。

1. D1データベースの作成

npx wrangler d1 create my-database

実行後にdatabase_idが発行される。

2. wrangler設定にバインディングを追加

# wrangler.toml
name = "my-app"
main = "src/index.ts"
compatibility_date = "2025-09-11"

[[d1_databases]]
binding = "DB"
database_name = "my-database"
database_id = "xxxxxxxxxxxxxxxxxx"
migrations_dir = "drizzle"

3. パッケージのインストール

npm install drizzle-orm
npm install -D drizzle-kit wrangler

4. スキーマの定義

// src/db/schema.ts
import { int, text, sqliteTable } from "drizzle-orm/sqlite-core";

export const posts = sqliteTable("posts", {
  id: int().primaryKey({ autoIncrement: true }),
  title: text().notNull(),
  body: text().notNull(),
  publishedAt: int({ mode: "timestamp" }),
});

5. drizzle.config.tsの設定

ここが重要なポイントになる。Drizzle Kitはふつう dialect だけでドライバーを自動選択してくれるが、D1はHTTP API経由の接続という特殊なケースに該当するため、driver: "d1-http" の明示的な指定が今も必須だ。dialect: "sqlite" を書いても driver を省略できる通常のSQLiteとは別物と考えてよい。

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

export default defineConfig({
  schema: "./src/db/schema.ts",
  out: "./drizzle",
  dialect: "sqlite",
  driver: "d1-http",
  dbCredentials: {
    accountId: process.env.CLOUDFLARE_ACCOUNT_ID!,
    databaseId: process.env.CLOUDFLARE_DATABASE_ID!,
    token: process.env.CLOUDFLARE_D1_TOKEN!,
  },
});

APIトークンはCloudflareダッシュボードの「My Profile → API Tokens」から、D1の編集権限を持つカスタムトークンを発行する。

6. マイグレーションの生成と適用

# マイグレーションSQLを生成
npx drizzle-kit generate

# ローカルD1に適用(開発環境)
npx wrangler d1 migrations apply my-database --local

# 本番D1に適用
npx wrangler d1 migrations apply my-database --remote

7. WorkerからD1を使う

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

export interface Env {
  DB: D1Database;
}

export default {
  async fetch(request: Request, env: Env) {
    const db = drizzle(env.DB, { schema });

    const allPosts = await db.select().from(schema.posts);
    return Response.json(allPosts);
  },
};

drizzle(env.DB) とバインディングを渡すだけで、あとは通常のDrizzle APIが使える。

ローカル開発の落とし穴

D1との組み合わせで最もはまりやすいのがローカル開発だ。

D1はCloudflare Workersの内側でしか動かない。ローカルのNode.jsスクリプトからD1に直接接続することはできない。ローカルでの wrangler dev 実行時、WranglerはSQLiteファイルを次のパスに生成する。

.wrangler/state/v3/d1/miniflare-D1DatabaseObject/<hash>.sqlite

このハッシュはバインディングのdatabase_idから導出されるが、予測不能な値になる。そのためDrizzle KitがこのファイルのパスをWranglerに尋ねる手段が存在せず、ローカルのSQLiteに対してDrizzle Kitでマイグレーションを走らせるのが厄介になっている。

Wranglerには --persist-to オプションがあり、ローカルデータの保存先ディレクトリを任意のパスに固定できる。チーム開発やCI/CDで同一の状態を共有したいときに有用だ。

npx wrangler dev --persist-to=./.wrangler/local-state

ただしこれはあくまで 保存先ディレクトリを変えるだけ で、その中に作られるSQLiteファイル名がハッシュベースであることは変わらない。Drizzle KitがそのパスをWranglerに問い合わせる手段はなく、ハッシュ問題の根本的な解決にはならない。

コミュニティではこの問題を解消するために @deox/drizzle-d1-utils のようなユーティリティが生まれており、WranglerのコンフィグからSQLiteファイルパスを動的に解決してDrizzle Kitに渡す仕組みを提供している。

ローカル開発のシンプルな回避策としてよく使われるパターンは、db:migrate:local では drizzle-kit migrate(ローカルSQLite向け)を使い、db:migrate:prod では wrangler d1 migrations apply --remote でWranglerに任せる構成だ。DrizzleとWranglerはマイグレーションファイルのフォーマットを共有するため、Drizzle Kitが生成したSQLをWranglerがそのまま読んで実行できる。

{
  "scripts": {
    "db:migrate:local": "drizzle-kit migrate",
    "db:migrate:prod": "wrangler d1 migrations apply DB --remote",
    "deploy": "npm run build && npm run db:migrate:prod && wrangler deploy"
  }
}

D1の制約を理解して使う

D1は銀の弾丸ではない。設計上の制約を把握しておく必要がある。

まず単一スレッド処理という点。D1データベースはDurable Objectとして動いており、クエリを1つずつ順番に処理する。スループットはクエリの実行速度に依存し、平均1ミリ秒のクエリなら毎秒約1000クエリ、100ミリ秒なら10クエリになる。高書き込みが必要なログ収集や分析系ワークロードには向かない。

次に書き込みの重さ。INSERTやUPDATEは複数のロケーションにわたって耐久性のある永続化が必要なため、読み取りより遅い傾向がある。数十万行を一括更新するような処理は1000行単位のバッチ処理に分割する必要がある。

そして1DBあたり10GBの上限は引き上げ不可。大量データを1つのDBに集約するアーキテクチャはD1に向いていない。前述のとおり、小さいDBを大量に作る水平スケールの設計思想と相性がよい。

逆に、サーバーレスAPIのバックエンドマルチテナントSaaSCloudflareエコシステムのフルスタックアプリ(Hono + D1、Astro + D1など)では非常に強力な選択肢になる。

2026年現在のポジション

DrizzleとD1の組み合わせはCloudflare Workersで何か作るときの「最初に検討するスタック」として定着しつつある。CloudflareのD1公式ドキュメントにはコミュニティプロジェクトのページがあり、Drizzleはそこに他のORMやクエリビルダーと並んで紹介されている。Drizzle側からもD1の公式ドキュメントへのリンクが整備されており、両者の接続ガイドは参照しやすい状態にある。

Drizzle自体の開発は活発で、Relational API v2の開発が進んでいる。型推論の複雑さやスケール時のTypeScript型インスタンス化コストに関する議論もコミュニティに存在するが、サーバーレス・エッジ用途においての実用性は確立されている。

私はClaude Codeを使った開発でこの構成を選ぶことが増えた。スキーマをTypeScriptで完結させられること、マイグレーションが明示的なSQLファイルとして残ること、バンドルサイズを気にしなくてよいこと。それぞれが小さな確信を積み重ねてくれる。

SQL的な感覚を残したままTypeScriptの型安全性を得たいなら、Drizzle × D1 は現時点でも十分に筋のよい選択だ。


参考リンク

Last updated