Drizzle で遊ぶ

このスクラップについて
以前から気になっていて触れていなかった Drizzle ORM を今更ながら触ってみよう。
Cloudflare Workers から D1 へアクセスするところまでは調べたい。

Getting Started
まずは普通に SQLite でローカルから試してみる。

パッケージのインストール
cd ~/workspace
mkdir scrap-drizzle
pnpm i drizzle-orm @libsql/client dotenv
pnpm i -D drizzle-kit tsx

環境変数ファイルの作成
touch .env
DB_FILE_NAME="file:local.db"

index.ts の作成
mkdir src
touch src/index.ts
import "dotenv/config";
import { drizzle } from "drizzle-orm/libsql";
const db = drizzle(process.env.DB_FILE_NAME!);

パッケージの追加
pnpm i -D @types/node

Biome の設定
pnpm i -D -E @biomejs/biome
pnpm biome init

schema.ts の作成
mkdir src/db
touch src/db/schema.ts
import { int, sqliteTable, text } from "drizzle-orm/sqlite-core";
export const usersTable = sqliteTable("users_table", {
id: int().primaryKey({ autoIncrement: true }),
name: text().notNull(),
age: int().notNull(),
email: text().notNull().unique(),
});

Drizzle config の作成
touch drizzle.config.ts
import "dotenv/config";
import { defineConfig } from "drizzle-kit";
export default defineConfig({
out: "./drizzle",
schema: "./src/db/schema.ts",
dialect: "sqlite",
dbCredentials: {
url: process.env.DB_FILE_NAME!,
},
});

DB 変更の適用
pnpm drizzle-kit push
No config path provided, using default 'drizzle.config.ts'
Reading config file '/Users/susukida/workspace/scrap-drizzle/drizzle.config.ts'
[✓] Pulling schema from database...
[✓] Changes applied

レコードの挿入と選択
import "dotenv/config";
import { eq } from "drizzle-orm";
import { drizzle } from "drizzle-orm/libsql";
import { usersTable } from "./db/schema";
const db = drizzle(process.env.DB_FILE_NAME!);
async function main() {
const user: typeof usersTable.$inferInsert = {
name: "John",
age: 30,
email: "john@example.com",
};
await db.insert(usersTable).values(user);
console.log("New user created!");
const users = await db.select().from(usersTable);
console.log("Getting all users from the database: ", users);
await db
.update(usersTable)
.set({ age: 31 })
.where(eq(usersTable.email, user.email));
console.log("User info updated!");
await db.delete(usersTable).where(eq(usersTable.email, user.email));
console.log("User deleted!");
}
main().catch((err) => console.error(err));

実行
pnpm tsx src/index.ts
New user created!
Getting all users from the database: [ { id: 1, name: 'John', age: 30, email: 'john@example.com' } ]
User info updated!
User deleted!

次のステップ
Cloudflare Workers で使ってみよう。

Cloudflare Workers で使う
cd workspace
pnpm create hono@latest -i -p pnpm -t cloudflare-workers scrap-drizzle-d1
cd scrap-drizzle-d1

D1 データベースの作成
pnpm wrangler d1 create scrap-drizzle-d1
⛅️ wrangler 4.36.0
───────────────────
✅ Successfully created DB 'scrap-drizzle-d1' in region APAC
Created your new D1 database.
To access your new D1 Database in your Worker, add the following snippet to your configuration file:
{
"d1_databases": [
{
"binding": "scrap_drizzle_d1",
"database_name": "scrap-drizzle-d1",
"database_id": "xxxx"
}
]
}

Biome 設定
pnpm i -D -E @biomejs/biome
pnpm biome init

wrangler.json の編集
{
"$schema": "node_modules/wrangler/config-schema.json",
"name": "scrap-drizzle-d1",
"main": "src/index.ts",
"compatibility_date": "2025-09-13",
"compatibility_flags": ["nodejs_compat"],
"d1_databases": [
{
"binding": "DB",
"database_name": "scrap-drizzle-d1",
"database_id": "xxxx"
}
]
}

チュートリアルを発見
遅ればせながらこちらに従おう。

パッケージのインストール
pnpm i drizzle-orm
pnpm i -D drizzle-kit dotenv

テーブルの作成
mkdir src/db
touch src/db/schema.ts
import { int, sqliteTable, text } from "drizzle-orm/sqlite-core";
export const usersTable = sqliteTable("users", {
id: int().primaryKey({ autoIncrement: true }),
name: text().notNull(),
age: int().notNull(),
email: text().notNull().unique(),
});

Drizzle config の作成
touch drizzle.config.ts
import "dotenv/config";
import { defineConfig } from "drizzle-kit";
export default defineConfig({
out: "./drizzle",
schema: "./src/db/schema.ts",
dialect: "sqlite",
driver: "d1-http",
dbCredentials: {
accountId: process.env.CLOUDFLARE_ACCOUNT_ID!,
databaseId: process.env.CLOUDFLARE_DATABASE_ID!,
token: process.env.CLOUDFLARE_D1_TOKEN!,
},
});

.env の作成
touch .env
CLOUDFLARE_ACCOUNT_ID="xxxx"
CLOUDFLARE_DATABASE_ID="yyyy"
CLOUDFLARE_D1_TOKEN="zzzz"
各値の取得方法についてはこちら。
To get accountId go to Workers & Pages -> Overview -> copy Account ID from the right sidebar.
To get databaseId open D1 database you want to connect to and copy Database ID.
To get token go to My profile -> API Tokens and create token with D1 edit permissions.

データベースの作成
pnpm drizzle-kit push
No config path provided, using default 'drizzle.config.ts'
Reading config file '/Users/susukida/workspace/scrap-drizzle-d1/drizzle.config.ts'
[✓] Pulling schema from database...
[✓] Changes applied

次のステップ
src/index.ts から D1 にアクセスしてみよう。

Hono から D1 へのアクセス

TypeScript 型生成
pnpm cf-typegen

cf-typegen は .env も見てくれる?
.dev.vars の方が優先されるが、.env も見てくれるようだ。

コーディング
import { drizzle } from "drizzle-orm/d1";
import { Hono } from "hono";
import { usersTable } from "./db/schema";
const app = new Hono<{
Bindings: CloudflareBindings;
}>();
app.get("/", async (c) => {
const db = drizzle(c.env.DB);
const users = await db.select().from(usersTable).all();
return c.json(users);
});
export default app;

エラー発生
Error: D1_ERROR: no such table: users: SQLITE_ERROR
おそらくローカルの方にはまだテーブルがないことが原因だろう。

ローカルにテーブル作成
マイグレーションファイルを生成して手動でマイグレーションするのが良さそう?

.sqlite ファイルを直接指定する方法も
ファイルは .wrangler/state/v3/d1/miniflare-D1DatabaseObject/xxxx.sqlite にある。

マイグレーションの生成
pnpm drizzle-kit generate
CREATE TABLE `users` (
`id` integer PRIMARY KEY AUTOINCREMENT NOT NULL,
`name` text NOT NULL,
`age` integer NOT NULL,
`email` text NOT NULL
);
--> statement-breakpoint
CREATE UNIQUE INDEX `users_email_unique` ON `users` (`email`);

マイグレーションの実行
{
"d1_databases": [
{
"migrations_dir": "./drizzle"
}
]
}
pnpm wrangler d1 migrations apply scrap-drizzle-d1 --local
⛅️ wrangler 4.36.0 (update available 4.37.0)
─────────────────────────────────────────────
Migrations to be applied:
┌────────────────────────────┐
│ name │
├────────────────────────────┤
│ 0000_absurd_emma_frost.sql │
└────────────────────────────┘
✔ About to apply 1 migration(s)
Your database may not be available to serve requests during the migration, continue? … yes
🌀 Executing on local database scrap-drizzle-d1 (xxxx) from .wrangler/state/v3/d1:
🌀 To execute on your remote database, add a --remote flag to your wrangler command.
🚣 3 commands executed successfully.
┌────────────────────────────┬────────┐
│ name │ status │
├────────────────────────────┼────────┤
│ 0000_absurd_emma_frost.sql │ ✅ │
└────────────────────────────┴────────┘

この状態で再挑戦
pnpm dev
curl http://localhost:8787/
[]

POST 追加
import { drizzle } from "drizzle-orm/d1";
import { Hono } from "hono";
import { usersTable } from "./db/schema";
const app = new Hono<{
Bindings: CloudflareBindings;
}>();
app.get("/users", async (c) => {
const db = drizzle(c.env.DB);
const users = await db.select().from(usersTable);
return c.json(users);
});
app.post("/users", async (c) => {
const db = drizzle(c.env.DB);
await db.insert(usersTable).values({
name: "John Doe",
age: 30,
email: "john.doe@example.com",
});
return c.status(201);
});
export default app;

動作確認
curl -X POST http://localhost:8787/users
curl http://localhost:8787/users | jq
[
{
"id": 1,
"name": "John Doe",
"age": 30,
"email": "john.doe.1757922881376@example.com"
}
]

デプロイ
pnpm deploy
Worker has workers.dev disabled, but 'workers_dev' is not in the config
警告メッセージが表示された。
wrangler.json に "workers_dev": true
を追記すれば消えそうだ。

リモートでの動作確認
curl https://scrap-drizzle-d1.tatsuyasusukida.workers.dev/users | jq
[]
curl -X POST https://scrap-drizzle-d1.tatsuyasusukida.workers.dev/users
curl https://scrap-drizzle-d1.tatsuyasusukida.workers.dev/users | jq
[
{
"id": 1,
"name": "John Doe",
"age": 30,
"email": "john.doe.1757923262413@example.com"
}
]

リモートのマイグレーション
気になったのでやってみる。
pnpm wrangler d1 migrations apply scrap-drizzle-d1
⛅️ wrangler 4.36.0 (update available 4.37.0)
─────────────────────────────────────────────
✅ No migrations to apply!
こちらは drizzle-kit push
を使ってやったのでうまくいかないかと思ったが大丈夫だった。
ただ怖いので wrangler の migrations 機能の方を使うことにしよう。

おわりに
ほんのさわりだったが無事に Cloudflare D1 を Drizzle で使用できることがわかって良かった。
D1 は有料プランであれば 10 GB のデータベースサイズまで利用できるので、ちょっとした個人的な Web アプリを作る程度であれば十分に利用できそうだ。
そろそろ Workers Paid プランを契約するときなのかも知れない。