第5章: エッジストレージ&DBのCLI活用術(実践チュートリアル)
前章(第4章)ではメモリ上にデータを保持するAPIを作成しました。本章では、これをエッジリレーショナルデータベース Cloudflare D1(サーバーレスSQLite) に移行し、データの永続化とマイグレーションのCLI運用をマスターします。また、キャッシュ用の KV とファイル保存用の R2 のCLI操作もハンズオンで習得します。
本チュートリアルのゴール
- D1(SQLite):
wrangler d1 createによるデータベース作成- マイグレーションSQLファイルの作成とローカル/リモート適用
- Hono からの型安全なプレースホルダー付きSQLクエリ(SQLインジェクション対策)
- KV(Key-Value):
- ネームスペース作成とバインディング
- CLIからのキャッシュ登録・参照・TTL(有効期限)設定
- R2(オブジェクトストレージ):
- バケット作成と下り転送量(Egress)0円のファイルアップロード/ダウンロード
Part 1: D1(SQLite)データベースハンズオン
【D1運用の基本サイクル】[Step 1] wrangler d1 create でDBを作成 ↓[Step 2] wrangler.jsonc に database_id を登録 ↓[Step 3] マイグレーションSQLを作成(0001_create_todos.sql) ↓[Step 4] ローカルDBに適用(wrangler d1 migrations apply --local) ↓[Step 5] HonoコードでSQLを実行・CRUD実装 ↓[Step 6] 本番DBに適用(wrangler d1 migrations apply --remote)Step 1: D1 データベースの新規作成
ターミナルで以下のコマンドを実行します。
npx wrangler d1 create my-todo-db出力例:
✅ Successfully created DB 'my-todo-db' in region APACCreated your database using D1's new storage backend.
[[d1_databases]]binding = "DB"database_name = "my-todo-db"database_id = "xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx"出力された binding と database_id をコピーしておきます。
Step 2: wrangler.jsonc へのバインディング設定
プロジェクトの wrangler.jsonc を開き、d1_databases 配列を追加します。
{ "name": "my-hono-api", "main": "src/index.ts", "compatibility_date": "2024-09-01", "compatibility_flags": [ "nodejs_compat" ], "d1_databases": [ { "binding": "DB", "database_name": "my-todo-db", "database_id": "xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx" // 先ほど出力されたID } ]}設定したら、型定義を自動更新します。
npx wrangler typesこれにより、Env 型に DB: D1Database が自動的に追加され、コード内でSQL実行関数(prepare(), bind(), all(), run())が型補完されるようになります。
Step 3: マイグレーションSQLの作成
スキーマ変更は手動で行わず、必ずマイグレーションファイルとして管理します。
npx wrangler d1 migrations create my-todo-db create_todos_tableコマンドを実行すると、migrations/0001_create_todos_table.sql というファイルが自動生成されます。このファイルを開いてテーブル定義を記述します。
-- migrations/0001_create_todos_table.sqlCREATE TABLE IF NOT EXISTS todos ( id INTEGER PRIMARY KEY AUTOINCREMENT, title TEXT NOT NULL, completed INTEGER NOT NULL DEFAULT 0, created_at TEXT NOT NULL DEFAULT (datetime('now', 'utc')));
-- 初期シードデータINSERT INTO todos (title, completed) VALUES ('D1データベースの構築', 1), ('エッジマイグレーションのテスト', 0);Step 4: ローカルDBへマイグレーション適用
まずはローカル開発環境(SQLiteエミュレータ)に対して適用します。
npx wrangler d1 migrations apply my-todo-db --localターミナルに確認プロンプトが表示されたら y を入力します。
ローカルDBのデータ確認(CLI)
npx wrangler d1 execute my-todo-db --local --command="SELECT * FROM todos;"初期シードデータが正しく登録されていることが確認できます。
Step 5: Hono から D1 を操作するCRUD実装
src/index.ts のTodo関連エンドポイントを、D1を使用した実装に書き換えます。
import { Hono } from 'hono';
type Bindings = Env; // wrangler types で D1Database が含まれる
const app = new Hono<{ Bindings: Bindings }>();
// 1. Todo一覧取得(D1からSELECT)app.get('/api/todos', async (c) => { const { results } = await c.env.DB.prepare( 'SELECT * FROM todos ORDER BY id DESC' ).all();
return c.json({ count: results.length, todos: results });});
// 2. 新規作成(D1へINSERT:プレースホルダーでSQLインジェクション防止)app.post('/api/todos', async (c) => { const { title } = await c.req.json<{ title: string }>();
if (!title || title.trim() === '') { return c.json({ error: 'タイトルを入力してください' }, 400); }
const result = await c.env.DB.prepare( 'INSERT INTO todos (title, completed) VALUES (?, 0) RETURNING *' ) .bind(title.trim()) .first();
return c.json(result, 201);});
// 3. 完了状態のトグル更新(UPDATE)app.patch('/api/todos/:id', async (c) => { const id = c.req.param('id'); const { completed } = await c.req.json<{ completed: boolean }>();
const result = await c.env.DB.prepare( 'UPDATE todos SET completed = ? WHERE id = ? RETURNING *' ) .bind(completed ? 1 : 0, id) .first();
if (!result) { return c.json({ error: 'Todoが見つかりません' }, 404); }
return c.json(result);});
// 4. 削除(DELETE)app.delete('/api/todos/:id', async (c) => { const id = c.req.param('id');
const { success } = await c.env.DB.prepare('DELETE FROM todos WHERE id = ?') .bind(id) .run();
return c.json({ success });});
export default app;Step 6: ローカルでの動作確認
ローカル開発サーバーを起動し、curlでテストします。
npx wrangler dev# 一覧取得curl http://localhost:8787/api/todos
# 新規作成curl -X POST http://localhost:8787/api/todos \ -H "Content-Type: application/json" \ -d '{"title": "D1で永続化成功!"}'サーバーを再起動してもデータが消えずに保持されていることが確認できます。
Step 7: 本番D1データベースへのマイグレーション適用
本番環境のエッジDBに対してスキーマを適用します。
npx wrangler d1 migrations apply my-todo-db --remoteあとは npx wrangler deploy を実行すれば、本番のD1データベースと接続されたAPIが全世界に展開されます。
Part 2: Cloudflare KV(Key-Value)のCLI活用
D1へのアクセス負荷を減らすため、APIレスポンスのキャッシュやユーザーセッションを保持するのに KV を活用します。
1. ネームスペース作成
npx wrangler kv namespace create "CACHE_STORE"2. wrangler.jsonc に追加
"kv_namespaces": [ { "binding": "CACHE_STORE", "id": "xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx" }]3. CLIからのデータ操作
# 1. データの書き込み(60秒の有効期限TTLを設定)npx wrangler kv key put --binding=CACHE_STORE "stats:today" '{"visitors": 1250}' --expiration-ttl=60
# 2. データの読み出しnpx wrangler kv key get --binding=CACHE_STORE "stats:today"
# 3. キーの一覧確認npx wrangler kv key list --binding=CACHE_STORE
# 4. データの削除npx wrangler kv key delete --binding=CACHE_STORE "stats:today"Part 3: Cloudflare R2(S3互換ストレージ)のCLI活用
画像やバックアップなどの大容量ファイルは、下り転送量(Egress)が完全無料のR2に保管します。
1. バケット作成
npx wrangler r2 bucket create app-uploads2. CLIでのファイルアップロード・取得
# ローカルの画像をR2へアップロードnpx wrangler r2 object put app-uploads/images/sample.png --file=./sample.png
# アップロードされたオブジェクトの確認npx wrangler r2 object get app-uploads/images/sample.png --file=./downloaded.png
# バケット内のファイル一覧npx wrangler r2 object list app-uploads3. 公開URLの設定
R2バケットは、カスタムドメインまたは r2.dev サブドメインを紐付けることで、全世界のエッジCDN経由で画像配信サーバーとして機能させることができます。
まとめ
- D1:
wrangler d1 migrationsでスキーマをコード管理し、prepare().bind()でSQLインジェクションを完全遮断。 - KV: 超高頻度な読み込みキャッシュをCLIから即座に点検。
- R2: AWS S3の転送量課金リスクをゼロにし、大容量ファイルをエッジから配信。
次章(第6章)では、このエッジ環境で動く「定期バッチ(Cron Triggers)」と「非同期メッセージキュー(Queues)」の実践に進みます。
💡 用語解説コラム
[!NOTE] マイグレーション (Migration)
データベースのテーブル構造(スキーマ)を、バージョン管理されたSQLファイル(0001_create_table.sql等)で段階的に変更・反映する仕組み。開発と本番のテーブル差分事故を防ぎます。
[!NOTE] TTL (Time To Live)
データが自動的に消滅するまでの有効期限(秒数)。Cloudflare KVではキーごとにTTLを設定でき、古いキャッシュを自動的にお掃除する際に重宝します。
[!NOTE] プレースホルダー (Prepared Statement)
SQL文中の可変部分を「?」にして値を安全に埋め込む技術。ユーザー入力値をそのままSQLに連結すると起こる「SQLインジェクション攻撃」を根本から防止します。