この記事は ひとりCloudflareを使い倒す Advent Calendar 2025 の5日目です
どうも、toreis-up です。
勉強をたくさんしたので、実際に手を動かしてみようと思います。
本記事では、D1 をいじります。
この記事の前まで、仕組みレベルまで調査するというかなりハイカロリーな記事が多く、下手すると続かない可能性がありますので、多少ローカロリーにしています。
行間は各自補完くださいませ。
また、この記事は使い倒すというより、使い倒すための前段階として触ってみることを考えています。
そのため、そんなにすごいことはしていません。チュートリアルレベルです。
Cloudflare D1 とは
Cloudflare D1 はサーバレス SQL データベースです。
正しくは、自分でサーバーを用意しなくていい、SQLite データベースです。
D1 is designed for horizontal scale out across multiple, smaller (10 GB) databases, such as per-user, per-tenant or per-entity databases. D1 allows you to build applications with thousands of databases at no extra cost for isolating with multiple databases. D1 pricing is based only on query and storage costs. ――― Cloudflare D1 Doc
D1 は水平スケールさせること向けに設計されています。
なので、ちっちゃい DB をたくさん作って、それぞれでデータを扱うのが良いらしいです。
SQLite だからかな…
Workers (w/Hono) から呼び出してみる
ヘッドレス Todo リストを作ってみます。
REST API で、TODO を作成するエンドポイントと TODO 一覧を出すエンドポイントを実装します。
テーブル構成は
- id INTEGER
- name TEXT
- completed INTEGER
です。(データベーススペシャリスト持ってるとは思えないくらいひどい)
初期化
ともあれ、Workers CLI を使って Hono 環境で初期化します。
pnpm create cloudflare@latest my-first-worker
category を Framework Starter にして、Hono で初期化します。
出来上がったファイルたちの中に wrangler.jsonc がいるので、以下のように書き換えます。
{
- "name": "<TBD>",
+ "name": "todo",
"main": "src/index.ts",
- "compatibility_date": "<TBD>",
+ "compatibility_date": "2025-12-05", // 今日の日付
- "assets": {
- "binding": "ASSETS",
- "directory": "./public"
- }
}
そして
pnpm wrangler d1 create [DATABASE_NAME]
を実行してデータベースを立てます。
これ以降、[DATABASE_NAME] は todo として扱っています。
なんか聞かれるので、Enter キーを連打します。
⛅️ wrangler 4.53.0
───────────────────
✅ Successfully created DB 'todo' 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": "todo",
"database_name": "todo",
"database_id": "xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx"
}
]
}
√ Would you like Wrangler to add it on your behalf? ... yes
√ What binding name would you like to use? ... todo
√ For local dev, do you want to connect to the remote resource instead of a local resource? ... no
こんな感じになったら ok です。
もう一度 wrangler.jsonc を開いて、変更を加えます。
{
"name": "todo",
"main": "src/index.ts",
"compatibility_date": "2025-12-05",
"d1_databases": [
{
"binding": "todo",
"database_name": "todo",
"database_id": "xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx",
+ "migrations_dir": "./drizzle"
}
]
}
pnpm run cf-typegen で型を生成します。
そして、pnpm run dev で動くか確認しておきましょう。
Drizzle ORM を使う
今回は、DB のスキーマを生成するのを Drizzle ORM に任せます。
公式 Docs にしたがって、導入します。
pnpm add drizzle-orm wrangler dotenv
pnpm add -D drizzle-kit tsx
そしたらば、src/db/schema.ts にスキーマを書き込みます。
import { sqliteTable, integer, text } from 'drizzle-orm/sqlite-core'
export const todos = sqliteTable('todos', {
id: integer('id').primaryKey({ autoIncrement: true }),
title: text('title').notNull(),
completed: integer('completed').$type<0 | 1>().notNull().default(0),
})
そして、src と同じ階層で drizzle.config.ts にコンフィグを書き込みます。
import "dotenv/config";
import { defineConfig } from "drizzle-kit";
export default defineConfig({
out: "./drizzle",
schema: "./src/db/schema.ts",
dialect: "sqlite"
});
で、以下のコマンドでマイグレーションファイルを作ります。
pnpm drizzle-kit generate
drizzle/ に、0000_hoge_fuga.sql みたいなファイルが出来上がっているはずです。
これはデータベースの設計書みたいなものと思ってもらって構いません。
そしたら、ローカル DB の形を変えていきます。
pnpm wrangler d1 migrations apply todo --local
⛅️ wrangler 4.53.0
───────────────────
Resource location: local
Use --remote if you want to access the remote instance.
Migrations to be applied:
┌────────────────────────────┐
│ name │
├────────────────────────────┤
│ 0000_goofy_dragon_lord.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 todo (xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx) from .wrangler\state\v3\d1:
🌀 To execute on your remote database, add a --remote flag to your wrangler command.
🚣 2 commands executed successfully.
┌────────────────────────────┬────────┐
│ name │ status │
├────────────────────────────┼────────┤
│ 0000_goofy_dragon_lord.sql │ ✅ │
└────────────────────────────┴────────┘
こうなれば ok です。
Hono から呼び出してみる
DB の設定が終わったので、実際に DB を呼び出してみましょう。
src/index.ts を以下のように変更します。
import { Hono } from "hono";
+ import { drizzle } from 'drizzle-orm/d1'
+ import { todos } from "./db/schema";
const app = new Hono<{ Bindings: CloudflareBindings }>();
app.get("/message", (c) => {
return c.text("Hello Hono!");
});
+ app.get("/todos", async (c) => {
+ const db = drizzle(c.env.todo);
+ const result = await db.select().from(todos).all();
+ return c.json(result);
+ })
export default app;
そしてサーバーを起動して、http://localhost:8787/todos にアクセスしてみます。
するとどうでしょう。
あれ、何も出てきません。
そらそうですね、データを入れていません。
ということで、データを入れるエンドポイントを立てます。
import { Hono } from "hono";
import { drizzle } from 'drizzle-orm/d1'
import { todos } from "./db/schema";
const app = new Hono<{ Bindings: CloudflareBindings }>();
app.get("/message", (c) => {
return c.text("Hello Hono!");
});
app.get("/todos", async (c) => {
const db = drizzle(c.env.todo);
const result = await db.select().from(todos).all();
return c.json(result);
})
+ app.post("/todo", async (c) => {
+ const db = drizzle(c.env.todo);
+ const { title } = await c.req.json();
+ const result = await db.insert(todos).values({ title }).returning();
+ return c.json(result);
})
export default app;
サーバーが立ち上がったら、リクエストを投げます。
ここでは Postman を使ってリクエストを投げています。
POST メソッド、localhost:8787/todo、Body は JSON で、
{
"title": "TEST"
}
として、Send すると…
リスポンスになにか返ってきました!
これこそ、DB に保存されたオブジェクトです!
もう一度 http://localhost:8787/todos にアクセスしてみます。
保存できてる!やったあ!
タスクを完了するエンドポイントを作る
タスクが出来上がったなら、完了しなければいけません。
ここでは、/todo/:id/complete に POST したら、タスクを完了するようにします。
import { Hono } from "hono";
import { drizzle } from 'drizzle-orm/d1'
import { todos } from "./db/schema";
+ import { eq } from "drizzle-orm";
const app = new Hono<{ Bindings: CloudflareBindings }>();
app.get("/message", (c) => {
return c.text("Hello Hono!");
});
app.get("/todos", async (c) => {
const db = drizzle(c.env.todo);
const result = await db.select().from(todos).all();
return c.json(result);
})
app.post("/todo", async (c) => {
const db = drizzle(c.env.todo);
const { title } = await c.req.json();
const result = await db.insert(todos).values({ title }).returning();
return c.json(result);
})
+ app.post("/todo/:id/complete", async (c) => {
+ const db = drizzle(c.env.todo);
+ const { id } = c.req.param();
+ const result = await db.update(todos).set({ completed: 1 }).where(eq(todos.id, Number(id))).returning();
+ return c.json(result);
+ });
export default app;
で、先程作成したタスクを完了してみましょう。
Postman で POST、localhost:8787/todo/1/complete にリクエストを送ります。
すると
complete が 1 になった TODO が返ってきました。
出来上がり!
マイグレーションしてみる
ここであなたは「タスクの期限も設定したい」と考えるようになります。
スキーマは次のように変わるでしょう。
import { sqliteTable, integer, text } from 'drizzle-orm/sqlite-core'
export const todos = sqliteTable('todos', {
id: integer('id').primaryKey({ autoIncrement: true }),
title: text('title').notNull(),
+ deadline: integer('deadline'), // 締め切り UNIX 時間
completed: integer('completed').$type<0 | 1>().notNull().default(0),
})
変えたら、データベースの構造も自分で変更しなければなりません。
pnpm drizzle-kit generate の出番です。
実行すると、
1 tables
todos 4 columns 0 indexes 0 fks
[✓] Your SQL migration file ➜ drizzle\0001_windy_millenium_guard.sql 🚀
と出ます。
drizzle\0001_hoge_fuga.sql の中身は
ALTER TABLE `todos` ADD `deadline` integer;
となっています。これは、todos テーブルに deadline って項目を追加してくださいね~ってことです。
で、pnpm wrangler d1 migrations apply todo --local で、ローカル DB の構造を更新します。
更新して TODO 一覧を見ると、
deadline に null が詰まっていますね。
deadline が増えたので、正解です。
あとはなんか色々追加したり足したり引いたりすると、TODO リストになります。
おわりに
だいぶローカロリーに終わってしまいましたが、Cloudflare Workers, Cloudflare D1, Hono, Drizzle の使い方がある程度わかりました。
この延長線上に、コンテンツ管理やサービスのデータベースなど、いろいろなものを作ることができます。
さて、何を作ろうかな… お楽しみに。




