TypeScript + Hono + Cloudflare WorkersでREST APIを実装する入門チュートリアル
なぜ Hono + Cloudflare Workers なのか
個人開発でREST APIを作るとき、サーバー管理が不要で・コールドスタートが速く・無料枠が広い構成はとても魅力的です。Cloudflare Workers はエッジで動くサーバーレス実行環境であり、Hono はその上で最高のDXを提供する超軽量TypeScriptフレームワークです。
主なメリットを整理すると:
| 比較軸 | Hono + CF Workers | Express + VPS |
|---|---|---|
| 冷起動 | ほぼゼロ(< 1ms) | 数十〜数百ms |
| スケール | 自動・無制限 | 手動設定が必要 |
| 月額費用 | 無料枠で十分(10万req/日) | 数百〜数千円 |
| TypeScript対応 | ビルトイン | 追加設定が必要 |
| デプロイ | wrangler deploy 1コマンド | CI/CD構築が必要 |
デメリットとして、実行時間の上限(CPU 10ms/リクエスト、無料プラン)や Node.js 固有 API が使えない点には注意が必要です。重い処理や長時間ジョブには向きません。
前提環境
- Node.js 18以上
npm/pnpmのいずれか- Cloudflareアカウント(無料)
Step 1: プロジェクトをスキャフォールド
npm create hono@latest my-api
# テンプレート選択: cloudflare-workers
cd my-api
npm install
生成されたディレクトリ構成:
my-api/
├── src/
│ └── index.ts # エントリポイント
├── wrangler.toml # CF Workers 設定
├── tsconfig.json
└── package.json
wrangler.toml の name と compatibility_date を確認しておきます。
name = "my-api"
main = "src/index.ts"
compatibility_date = "2024-11-01"
Step 2: 基本的なルーティングを実装
src/index.ts を以下のように書き換えます。
import { Hono } from 'hono'
import { cors } from 'hono/cors'
import { logger } from 'hono/logger'
const app = new Hono()
// ミドルウェア
app.use('*', logger())
app.use('/api/*', cors())
// ヘルスチェック
app.get('/', (c) => c.json({ status: 'ok' }))
// --- items リソース ---
app.get('/api/items', (c) => {
return c.json({ items: [] })
})
app.get('/api/items/:id', (c) => {
const id = c.req.param('id')
return c.json({ id, name: 'Sample Item' })
})
app.post('/api/items', async (c) => {
const body = await c.req.json()
return c.json({ created: body }, 201)
})
export default app
ローカルで動作確認:
npm run dev
# → http://localhost:8787 で起動
curl http://localhost:8787/api/items
# → {"items":[]}
Step 3: Zod でリクエストバリデーション
Honoには @hono/zod-validator という公式バリデーションミドルウェアがあります。
npm install zod @hono/zod-validator
スキーマ定義とバリデーションをルートに組み込みます:
import { zValidator } from '@hono/zod-validator'
import { z } from 'zod'
const itemSchema = z.object({
name: z.string().min(1).max(100),
price: z.number().positive(),
})
app.post(
'/api/items',
zValidator('json', itemSchema, (result, c) => {
if (!result.success) {
return c.json({ error: result.error.flatten() }, 400)
}
}),
async (c) => {
const { name, price } = c.req.valid('json')
// DB保存処理(後述)
return c.json({ name, price }, 201)
}
)
バリデーション失敗時は自動的に400エラーが返るため、コントローラ側でのガード記述が不要になります。
Step 4: Cloudflare D1(SQLite)を接続する
D1はCloudflareが提供するエッジ向けSQLiteサービスです。Workers Bindingで直接アクセスできます。
D1データベースを作成
npx wrangler d1 create my-api-db
出力されたデータベースIDを wrangler.toml に追記:
[[d1_databases]]
binding = "DB"
database_name = "my-api-db"
database_id = "xxxx-xxxx-xxxx" # 実際のIDに差し替え
マイグレーションを作成・実行
# ローカル
npx wrangler d1 execute my-api-db --local \
--command "CREATE TABLE IF NOT EXISTS items (id INTEGER PRIMARY KEY AUTOINCREMENT, name TEXT NOT NULL, price INTEGER NOT NULL);"
TypeScript から D1 を操作
src/index.ts にBindingの型を追加します:
type Bindings = {
DB: D1Database
}
const app = new Hono<{ Bindings: Bindings }>()
// 一覧取得
app.get('/api/items', async (c) => {
const { results } = await c.env.DB.prepare(
'SELECT * FROM items ORDER BY id DESC'
).all()
return c.json({ items: results })
})
// 作成
app.post(
'/api/items',
zValidator('json', itemSchema),
async (c) => {
const { name, price } = c.req.valid('json')
const result = await c.env.DB.prepare(
'INSERT INTO items (name, price) VALUES (?, ?) RETURNING *'
)
.bind(name, price)
.first()
return c.json(result, 201)
}
)
c.env.DB 経由でD1に型安全にアクセスできます。プリペアドステートメントでSQLインジェクションも防止できます。
Step 5: デプロイ
# D1をリモートにもマイグレーション
npx wrangler d1 execute my-api-db \
--command "CREATE TABLE IF NOT EXISTS items (id INTEGER PRIMARY KEY AUTOINCREMENT, name TEXT NOT NULL, price INTEGER NOT NULL);"
# デプロイ
npm run deploy
# → https://my-api.<your-subdomain>.workers.dev
デプロイ後、URLは wrangler deploy の出力に表示されます。カスタムドメインの設定はCloudflareダッシュボードから可能です。
Step 6: ルートをファイル分割する(応用)
APIが大きくなってきたら、Honoの app.route() でルートを分割できます。
// src/routes/items.ts
import { Hono } from 'hono'
type Bindings = { DB: D1Database }
const items = new Hono<{ Bindings: Bindings }>()
items.get('/', async (c) => { /* ... */ })
items.post('/', async (c) => { /* ... */ })
export default items
// src/index.ts
import items from './routes/items'
app.route('/api/items', items)
Express.js ライクな感覚でルートを整理できます。
まとめ
Hono + Cloudflare Workers + D1 の組み合わせで、以下が実現できます:
- TypeScript完全対応のREST APIをゼロから構築
- Zodによる型安全なバリデーション
- D1(SQLite)による永続化
wrangler deploy1コマンドデプロイ
個人開発・小規模プロダクトであれば無料枠で十分運用できるコスパ最強スタックです。HonoはCloudflare Workers以外にもDeno・Bun・Node.jsで動くため、学習コストが将来にも活きます。
TypeScriptやCloudflare周辺の深い理解を体系的に身につけたい方には、書籍での学習も効果的です。
プログラミングをさらに体系的に学びたい・スキルアップを加速したい方は、メンタリング型スクールも選択肢の一つです。