Claude Code × Cloudflare D1:エッジSQLiteをWrangler CLIで自在に操るワークフロー
はじめに
Cloudflare WorkersでAPIを作ろうとして、「データベースをどこに置けばいいか」で詰まった経験はないだろうか。TCPが使えないエッジ環境にNode.jsのDBドライバーを持ち込もうとすると、接続の壁にぶつかる。Neonや外部DBにHTTPで繋げる方法はあるが、Cloudflareのエコシステムの中で完結させたいケースも多い。
Cloudflare D1はその答えの一つだ。Cloudflareのエッジネットワーク上で動くマネージドSQLiteで、Workersからc.env.DBでアクセスできる。Wrangler CLIとClaude Codeを組み合わせると、DBの作成・マイグレーション・Honoとの接続まで一気通貫で進められる。
Cloudflare D1とは——エッジで動くマネージドSQLite
D1は2024年4月にGA(一般提供開始)を迎えたマネージドSQLiteサービスだ。GAと同時にデータベースサイズ上限が10GBに拡張され、1アカウントあたり最大50,000データベースが利用可能になった(上限は公式ドキュメントで最新値を確認してほしい)。
設計の核心は「SQLiteのSQL方言をそのまま使える」という点だ。Workersのコードからはc.env.DB.prepare('SELECT ...').bind(id).first()というシンプルな形でSQLが実行できる。エッジ環境でのDB接続を別途設計する必要がない。
Time Travel機能(過去30日以内の任意の時点にDBを復元)も提供されているため、マイグレーションの失敗時のリカバリーが取れる点もプロダクション利用の安心材料になる。
ローカル開発ではwrangler devがSQLiteのローカルインスタンスを自動起動する。本番と同じSQL方言でローカルでもテストできるため、「ローカルでは動いたのに本番で失敗した」というDB方言の差異問題が起きにくい。
料金は現行プランを公式(developers.cloudflare.com/d1/platform/pricing/)で確認してほしいが、Freeプランで相当量のリクエストを賄えるため、個人開発や小規模チームでの試験的利用から始めやすい。
D1プロジェクトのセットアップ
DBの作成とwrangler.tomlの設定
D1の運用はすべてWrangler CLIを介して行う。Claude CodeはBashツールでWranglerコマンドを直接実行できるため、DBの作成からwrangler.tomlの更新まで依頼できる。
# 1. D1データベースを作成(Cloudflareアカウントに登録される)
npx wrangler d1 create my-app-db
# 2. 出力されたdatabase_idをwrangler.tomlに記入# wrangler.toml
name = "my-hono-app"
main = "src/index.ts"
compatibility_date = "2024-09-26"
[[d1_databases]]
binding = "DB"
database_name = "my-app-db"
database_id = "xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx"
migrations_dir = "migrations"binding = "DB"がコード内での参照名になる。Honoではc.env.DBでアクセスする。
マイグレーションの作成と適用
マイグレーションはSQLファイルで管理する:
# マイグレーションファイルを作成
npx wrangler d1 migrations create my-app-db "create_users_table"
# ローカルに適用(開発中)
npx wrangler d1 migrations apply my-app-db --local
# 本番環境に適用
npx wrangler d1 migrations apply my-app-db --remote生成されるSQLファイルを直接編集する:
-- migrations/0001_create_users_table.sql
CREATE TABLE IF NOT EXISTS users (
id TEXT PRIMARY KEY,
email TEXT UNIQUE NOT NULL,
name TEXT NOT NULL,
created_at INTEGER NOT NULL DEFAULT (unixepoch())
);
CREATE INDEX idx_users_email ON users(email);Claude Codeへの依頼例:
wrangler.tomlに記載のdatabase_idを使って、
usersテーブルを作成するマイグレーションを生成し、
ローカルに適用してください。
スキーマ: id TEXT PRIMARY KEY, email TEXT UNIQUE, created_at INTEGERClaude Codeがマイグレーションファイルの生成からwrangler d1 migrations apply --localの実行まで行う。--localと--remoteの切り替えをCLAUDE.mdに書いておくと、「本番に適用したら?」と聞いてからしか--remoteを実行しない運用が作れる。
Hono + D1でAPIを作る
生SQLパターン
D1の基本的なアクセスはprepare().bind().run()でのプリペアドステートメントだ:
// src/index.ts
import { Hono } from 'hono'
type Bindings = {
DB: D1Database
}
const app = new Hono<{ Bindings: Bindings }>()
app.get('/users', async (c) => {
const { results } = await c.env.DB.prepare(
'SELECT * FROM users ORDER BY created_at DESC LIMIT 20'
).run()
return c.json(results)
})
app.get('/users/:id', async (c) => {
const id = c.req.param('id')
const user = await c.env.DB.prepare(
'SELECT * FROM users WHERE id = ?'
).bind(id).first()
if (!user) return c.json({ error: 'Not found' }, 404)
return c.json(user)
})
app.post('/users', async (c) => {
const { email, name } = await c.req.json()
const id = crypto.randomUUID()
await c.env.DB.prepare(
'INSERT INTO users (id, email, name, created_at) VALUES (?, ?, ?, ?)'
).bind(id, email, name, Date.now()).run()
return c.json({ id, email, name }, 201)
})
export default appprepare().bind()がSQLインジェクション対策の基本パターンだ。CLAUDE.mdに「生SQLを書く場合はprepare().bind()を使う」と書いておくと、Claude Codeが文字列結合でSQLを組み立てるコードを生成しなくなる。
Drizzle ORM + D1パターン
型安全なクエリが必要な場合はDrizzle ORMと組み合わせる:
// src/db/schema.ts
import { text, integer, sqliteTable } from 'drizzle-orm/sqlite-core'
export const users = sqliteTable('users', {
id: text('id').primaryKey(),
email: text('email').unique().notNull(),
name: text('name').notNull(),
createdAt: integer('created_at').notNull()
.$defaultFn(() => Math.floor(Date.now() / 1000)),
})// src/index.ts
import { drizzle } from 'drizzle-orm/d1'
import { users } from './db/schema'
import { eq } from 'drizzle-orm'
app.get('/users/:id', async (c) => {
const db = drizzle(c.env.DB)
const id = c.req.param('id')
const result = await db.select().from(users).where(eq(users.id, id)).get()
if (!result) return c.json({ error: 'Not found' }, 404)
return c.json(result)
})Drizzle Kitでマイグレーションを生成する場合の設定(driverプロパティ名と値は最新のDrizzle Kitの仕様を公式ドキュメントで確認してほしい):
// drizzle.config.ts
import type { Config } from 'drizzle-kit'
export default {
schema: './src/db/schema.ts',
out: './migrations',
dialect: 'sqlite',
driver: 'd1-http', // 正確なプロパティ名はDrizzle Kit公式で確認すること
dbCredentials: {
accountId: process.env.CLOUDFLARE_ACCOUNT_ID!,
databaseId: process.env.CLOUDFLARE_DATABASE_ID!,
token: process.env.CLOUDFLARE_D1_TOKEN!,
},
} satisfies ConfigDrizzleでマイグレーションファイルを生成し、Wranglerで適用するという2段階の流れになる:
npx drizzle-kit generate
npx wrangler d1 migrations apply DB --localClaude Code × D1の実践ワークフロー
CLAUDE.mdへのガイドライン記述
## Cloudflare D1 ガイドライン
### Bindings
- DBバインディング名: DB(c.env.DB でアクセス)
- wrangler.tomlのd1_databases.binding = "DB"
### クエリ
- 生SQLの場合: prepare().bind().run() を使う(SQLインジェクション防止)
- ORMの場合: drizzle-orm/d1 を使う
### マイグレーション
- ファイル: migrations/NNNN_description.sql
- ローカル適用: npx wrangler d1 migrations apply DB --local
- 本番適用(確認後のみ): npx wrangler d1 migrations apply DB --remote
### ローカル開発
- npx wrangler dev でローカルSQLiteが自動起動
- .wrangler/state/v3/d1/ にローカルDBが保存される「本番適用(確認後のみ)」という一文を入れておくことで、Claude Codeが--remoteを実行する前に確認を求めるパターンを意識させられる。
まとめ
D1がClaude Codeとの組み合わせで際立つのは、「Wrangler CLIのコマンドをClaude Codeが直接実行できる」という点だ。DBの作成・マイグレーションの生成・ローカル適用まで、会話の中で進められる。
試すならnpx wrangler d1 createからDBを作り、CLAUDE.mdにD1のガイドラインを書いてから「usersテーブルを作るマイグレーションを生成してローカルに適用して」と依頼してみてほしい。エッジ環境でのDB操作の体験が、想像より簡単だと気づくはずだ。

コメント