株式会社オブライト
Software Dev2026-04-08

Hono × Cloudflare Workers完全ガイド — エッジで動く高速APIをサーバーレスで構築する方法【2026年版】

HonoはCloudflare Workersのために生まれたフレームワーク。世界300+都市のエッジでコールドスタート5ms以下・ゼロ運用コストのAPIを構築。D1・KV・R2・Workers AIとの統合からデプロイまで徹底解説。


HonoとCloudflare Workersの相性 — エッジで最速のAPIを実現

HonoはCloudflare Workersのために生まれたフレームワークです。世界300以上の都市に展開するCloudflareのエッジネットワーク上でネイティブに実行され、コールドスタート5ms以下・ゼロ運用コストのAPIを実現します。従来のサーバーベースAPIと比較して、インフラ管理が不要でグローバルに低レイテンシを達成できます。

Loading diagram...

環境構築手順 — プロジェクト作成からwranglerセットアップまで

bash
# プロジェクト作成(cloudflare-workersテンプレートを選択)
npm create hono@latest my-edge-api
# テンプレート選択: cloudflare-workers

cd my-edge-api
npm install

# Wrangler CLIのグローバルインストール(未導入の場合)
npm install -g wrangler

# Cloudflareアカウントへのログイン
wrangler login

# ローカル開発サーバー起動
npm run dev
# => http://localhost:8787

wrangler.toml設定例 — Bindingsの定義

toml
# wrangler.toml
name = "my-edge-api"
main = "src/index.ts"
compatibility_date = "2024-01-01"

# KV Namespaceのバインディング
[[kv_namespaces]]
binding = "CACHE"
id = "your-kv-namespace-id"

# D1 Databaseのバインディング
[[d1_databases]]
binding = "DB"
database_name = "my-database"
database_id = "your-d1-database-id"

# R2 Bucketのバインディング
[[r2_buckets]]
binding = "STORAGE"
bucket_name = "my-bucket"

# Workers AIのバインディング
[ai]
binding = "AI"

# 環境変数
[vars]
ENVIRONMENT = "production"

Bindingsの型定義 — TypeScriptで型安全な実装

ts
// src/index.ts
import { Hono } from 'hono'

// Bindingsの型定義
type Bindings = {
  DB: D1Database
  CACHE: KVNamespace
  STORAGE: R2Bucket
  AI: Ai
  ENVIRONMENT: string
}

// 型付きHonoインスタンス
const app = new Hono<{ Bindings: Bindings }>()

app.get('/', (c) => {
  // c.env.DBなどに型補完が効く
  const env = c.env.ENVIRONMENT
  return c.json({ env, message: 'Hello from the edge!' })
})

export default app

D1(SQLiteデータベース)連携 — CRUD操作の完全実装

ts
// D1 CRUD操作の実装例
app.get('/api/users', async (c) => {
  const result = await c.env.DB
    .prepare('SELECT * FROM users LIMIT 100')
    .all()
  return c.json(result.results)
})

app.get('/api/users/:id', async (c) => {
  const id = c.req.param('id')
  const user = await c.env.DB
    .prepare('SELECT * FROM users WHERE id = ?')
    .bind(id)
    .first()
  if (!user) return c.json({ error: 'Not found' }, 404)
  return c.json(user)
})

app.post('/api/users', async (c) => {
  const { name, email } = await c.req.json()
  const result = await c.env.DB
    .prepare('INSERT INTO users (name, email) VALUES (?, ?)')
    .bind(name, email)
    .run()
  return c.json({ id: result.meta.last_row_id }, 201)
})

app.delete('/api/users/:id', async (c) => {
  const id = c.req.param('id')
  await c.env.DB
    .prepare('DELETE FROM users WHERE id = ?')
    .bind(id)
    .run()
  return c.json({ deleted: true })
})

KV(Key-Value)連携 — キャッシュ実装

ts
// KVを使ったキャッシュ実装
app.get('/api/products/:id', async (c) => {
  const id = c.req.param('id')
  const cacheKey = `product:${id}`

  // キャッシュから取得
  const cached = await c.env.CACHE.get(cacheKey, 'json')
  if (cached) {
    c.header('X-Cache', 'HIT')
    return c.json(cached)
  }

  // DBから取得してキャッシュに保存
  const product = await c.env.DB
    .prepare('SELECT * FROM products WHERE id = ?')
    .bind(id)
    .first()

  if (!product) return c.json({ error: 'Not found' }, 404)

  // TTL 3600秒でキャッシュ
  await c.env.CACHE.put(cacheKey, JSON.stringify(product), {
    expirationTtl: 3600
  })

  c.header('X-Cache', 'MISS')
  return c.json(product)
})

R2(オブジェクトストレージ)連携 — ファイルアップロード/ダウンロード

ts
// R2ファイルアップロード
app.put('/api/files/:filename', async (c) => {
  const filename = c.req.param('filename')
  const body = await c.req.arrayBuffer()
  const contentType = c.req.header('content-type') ?? 'application/octet-stream'

  await c.env.STORAGE.put(filename, body, {
    httpMetadata: { contentType }
  })

  return c.json({ uploaded: filename })
})

// R2ファイルダウンロード
app.get('/api/files/:filename', async (c) => {
  const filename = c.req.param('filename')
  const object = await c.env.STORAGE.get(filename)

  if (!object) return c.json({ error: 'Not found' }, 404)

  const headers = new Headers()
  object.writeHttpMetadata(headers)
  headers.set('etag', object.httpEtag)

  return c.body(object.body, { headers })
})

Workers AI連携 — LLM・画像生成・埋め込みベクトル

ts
// LLMテキスト生成
app.post('/api/ai/chat', async (c) => {
  const { message } = await c.req.json()

  const response = await c.env.AI.run('@cf/meta/llama-3.1-8b-instruct', {
    messages: [{ role: 'user', content: message }]
  })

  return c.json({ reply: response.response })
})

// テキスト埋め込み(RAGなど)
app.post('/api/ai/embed', async (c) => {
  const { text } = await c.req.json()

  const embeddings = await c.env.AI.run('@cf/baai/bge-small-en-v1.5', {
    text: [text]
  })

  return c.json({ vector: embeddings.data[0] })
})

Cron Triggers — 定期実行ジョブの実装

ts
// src/index.ts
import { Hono } from 'hono'

type Bindings = { DB: D1Database }

const app = new Hono<{ Bindings: Bindings }>()

// 通常のHTTPハンドラ
app.get('/', (c) => c.text('API is running'))

// Cron Triggerハンドラ(scheduled export)
export default {
  fetch: app.fetch,
  async scheduled(event: ScheduledEvent, env: Bindings) {
    console.log('Cron triggered:', event.cron)
    // 毎日0時に古いデータを削除
    if (event.cron === '0 0 * * *') {
      await env.DB
        .prepare('DELETE FROM logs WHERE created_at < datetime("now", "-30 days")')
        .run()
    }
  }
}

`wrangler.toml` にCronスケジュールを追加:

toml
[[triggers.crons]]
crons = ["0 0 * * *"]

デプロイ手順 — 本番環境への反映

bash
# 本番デプロイ
npx wrangler deploy

# 特定環境へのデプロイ
npx wrangler deploy --env production

# 独自ドメインの設定(wrangler.tomlに追記)
# [[routes]]
# pattern = "api.example.com/*"
# zone_name = "example.com"

# デプロイ後の確認
npx wrangler tail  # リアルタイムログ確認

ローカル開発 — リモートモードとローカルモード

bash
# ローカルモード(ローカルのD1・KVシミュレーター使用)
npx wrangler dev

# リモートモード(本番のD1・KVに接続して開発)
npx wrangler dev --remote

# ポートを指定
npx wrangler dev --port 9000

D1データベースをローカル環境で初期化する場合:

bash
# マイグレーションファイルの作成
npx wrangler d1 migrations create my-database create-users

# ローカルD1への適用
npx wrangler d1 migrations apply my-database --local

# 本番D1への適用
npx wrangler d1 migrations apply my-database

シークレット管理 — wrangler secret put の使い方

bash
# シークレットの設定(プロンプトで値を入力)
npx wrangler secret put JWT_SECRET
npx wrangler secret put DATABASE_URL

# シークレット一覧の確認
npx wrangler secret list

# シークレットの削除
npx wrangler secret delete JWT_SECRET

コード内でシークレットにアクセスする場合は、Bindingsに型を追加します:

ts
type Bindings = {
  DB: D1Database
  JWT_SECRET: string  // wrangler secret putで設定した値
}

モニタリングとログ — wrangler tail・Cloudflare Analytics

bash
# リアルタイムログストリーム
npx wrangler tail

# フィルタリング(エラーのみ)
npx wrangler tail --status error

# JSON形式で出力
npx wrangler tail --format json

Cloudflare DashboardのWorkersセクションでは、リクエスト数・エラー率・CPU使用時間・エッジレイテンシをリアルタイムで確認できます。

料金体系 — 無料枠と有料プラン

Cloudflare Workersは非常に寛大な無料枠が用意されており、小規模プロジェクトであれば無料で運用が可能です。

プラン料金リクエストCPU時間
無料0円/月10万リクエスト/日10ms/リクエスト
Workers Paid約750円/月(約5ドル)1,000万リクエスト/月30ms/リクエスト
Workers Paid(超過)0.15ドル/100万リク追加課金追加課金
Workers Unbound使用量ベース無制限最大30秒/リクエスト

KV・D1・R2にも個別の無料枠と従量課金が設定されています。

本番運用のベストプラクティス

ts
import { Hono } from 'hono'
import { cors } from 'hono/cors'
import { logger } from 'hono/logger'
import { HTTPException } from 'hono/http-exception'

const app = new Hono<{ Bindings: Bindings }>()

// セキュリティヘッダーとCORS
app.use('*', cors({ origin: ['https://yourdomain.com'] }))
app.use('*', logger())

// グローバルエラーハンドリング
app.onError((err, c) => {
  if (err instanceof HTTPException) {
    return c.json({ error: err.message }, err.status)
  }
  console.error('Unhandled error:', err)
  return c.json({ error: 'Internal Server Error' }, 500)
})

// 404ハンドリング
app.notFound((c) => c.json({ error: 'Not Found' }, 404))

// シンプルなレート制限(KVベース)
app.use('/api/*', async (c, next) => {
  const ip = c.req.header('CF-Connecting-IP') ?? 'unknown'
  const key = `ratelimit:${ip}`
  const count = parseInt(await c.env.CACHE.get(key) ?? '0')

  if (count > 100) {
    throw new HTTPException(429, { message: 'Too Many Requests' })
  }

  await c.env.CACHE.put(key, String(count + 1), { expirationTtl: 60 })
  await next()
})

export default app

よくある質問(FAQ)

Q1. Cloudflare WorkersのCPU制限は問題になりませんか? 無料プランは10ms、有料プランは30msのCPU制限があります。重いデータ処理は避け、I/Oバウンドな処理(DB・KV操作)中心の設計にすることで制限を回避できます。Workers Unboundプランでは最大30秒まで延長可能です。 Q2. Node.jsのネイティブモジュール(fs、crypto等)は使えますか? fsは使えませんが、cryptoはWeb Crypto APIとして利用可能です。Node.jsの互換レイヤー(nodejs_compat フラグ)を有効化することで多くのNode.jsモジュールが動作します。 Q3. WebSocketは対応していますか? はい。CloudflareはWebSocket接続をサポートしており、Honoの `upgradeWebSocket` ヘルパーで実装できます。Durable Objectsと組み合わせることでリアルタイムアプリも構築可能です。 Q4. D1はどの程度のデータ量まで対応できますか? 1データベースあたり最大10GBまで対応しています(2026年現在)。大規模データが必要な場合は、Cloudflare Hyperdrive経由で外部PostgreSQL・MySQLに接続することを推奨します。 Q5. 既存のExpressアプリをHono×Cloudflare Workersに移行できますか? APIの設計思想が近いため移行は比較的容易です。ただし、Node.js固有のモジュール(path、fs等)の置き換えが必要です。段階的移行として、まず `@hono/node-server` でHonoに移行し、その後Workersに移行する手順が推奨されます。 Q6. Cloudflare WorkersでPrismaは使えますか? PrismaはWrangler D1ドライバー(@prisma/adapter-d1)に対応しており、D1と組み合わせて使用可能です。ただし起動時間の観点から、より軽量なDrizzle ORMの使用を推奨します。

OflightでHono × Cloudflare Workersの開発を加速させませんか

OflightはHonoおよびCloudflare Workersを活用したエッジAPIの設計・開発・運用支援を提供しています。D1・KV・R2・Workers AIを組み合わせたサーバーレスバックエンドの構築から、既存インフラのエッジへの移行まで幅広く対応します。まずはお気軽にご相談ください。 ソフトウェア開発サービスの詳細はこちら

お気軽にご相談ください

お問い合わせ