コンテンツにスキップ

第5章: エッジストレージ&DBのCLI活用術(実践チュートリアル)

前章(第4章)ではメモリ上にデータを保持するAPIを作成しました。本章では、これをエッジリレーショナルデータベース Cloudflare D1(サーバーレスSQLite) に移行し、データの永続化とマイグレーションのCLI運用をマスターします。また、キャッシュ用の KV とファイル保存用の R2 のCLI操作もハンズオンで習得します。


本チュートリアルのゴール

  1. D1(SQLite):
    • wrangler d1 create によるデータベース作成
    • マイグレーションSQLファイルの作成とローカル/リモート適用
    • Hono からの型安全なプレースホルダー付きSQLクエリ(SQLインジェクション対策)
  2. KV(Key-Value):
    • ネームスペース作成とバインディング
    • CLIからのキャッシュ登録・参照・TTL(有効期限)設定
  3. 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 データベースの新規作成

ターミナルで以下のコマンドを実行します。

Terminal window
npx wrangler d1 create my-todo-db

出力例:

✅ Successfully created DB 'my-todo-db' in region APAC
Created your database using D1's new storage backend.
[[d1_databases]]
binding = "DB"
database_name = "my-todo-db"
database_id = "xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx"

出力された bindingdatabase_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
}
]
}

設定したら、型定義を自動更新します。

Terminal window
npx wrangler types

これにより、Env 型に DB: D1Database が自動的に追加され、コード内でSQL実行関数(prepare(), bind(), all(), run())が型補完されるようになります。


Step 3: マイグレーションSQLの作成

スキーマ変更は手動で行わず、必ずマイグレーションファイルとして管理します。

Terminal window
npx wrangler d1 migrations create my-todo-db create_todos_table

コマンドを実行すると、migrations/0001_create_todos_table.sql というファイルが自動生成されます。このファイルを開いてテーブル定義を記述します。

-- migrations/0001_create_todos_table.sql
CREATE 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エミュレータ)に対して適用します。

Terminal window
npx wrangler d1 migrations apply my-todo-db --local

ターミナルに確認プロンプトが表示されたら y を入力します。

ローカルDBのデータ確認(CLI)

Terminal window
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でテストします。

Terminal window
npx wrangler dev
Terminal window
# 一覧取得
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に対してスキーマを適用します。

Terminal window
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. ネームスペース作成

Terminal window
npx wrangler kv namespace create "CACHE_STORE"

2. wrangler.jsonc に追加

"kv_namespaces": [
{
"binding": "CACHE_STORE",
"id": "xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx"
}
]

3. CLIからのデータ操作

Terminal window
# 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. バケット作成

Terminal window
npx wrangler r2 bucket create app-uploads

2. CLIでのファイルアップロード・取得

Terminal window
# ローカルの画像を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-uploads

3. 公開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インジェクション攻撃」を根本から防止します。