💡 Tips

TypeScript + Hono + Cloudflare WorkersでREST APIを実装する入門チュートリアル

なぜ Hono + Cloudflare Workers なのか

個人開発でREST APIを作るとき、サーバー管理が不要で・コールドスタートが速く・無料枠が広い構成はとても魅力的です。Cloudflare Workers はエッジで動くサーバーレス実行環境であり、Hono はその上で最高のDXを提供する超軽量TypeScriptフレームワークです。

主なメリットを整理すると:

比較軸Hono + CF WorkersExpress + 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.tomlnamecompatibility_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 deploy 1コマンドデプロイ

個人開発・小規模プロダクトであれば無料枠で十分運用できるコスパ最強スタックです。HonoはCloudflare Workers以外にもDeno・Bun・Node.jsで動くため、学習コストが将来にも活きます。

TypeScriptやCloudflare周辺の深い理解を体系的に身につけたい方には、書籍での学習も効果的です。

📚 おすすめ書籍

TypeScriptハンズオン

TypeScriptを基礎から体系的に学べる一冊

Amazonで見る →

プログラミングをさらに体系的に学びたい・スキルアップを加速したい方は、メンタリング型スクールも選択肢の一つです。