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

Hono × Cloudflare Workers Complete Guide — Build Lightning-Fast Edge APIs Serverlessly [2026]

Hono was born for Cloudflare Workers. Build APIs with sub-5ms cold starts and zero ops costs across 300+ edge locations worldwide. Complete guide covering D1, KV, R2, Workers AI integration, and deployment.


Hono and Cloudflare Workers — A Perfect Match for Edge APIs

Hono was born for Cloudflare Workers. Running natively on Cloudflare's edge network spanning 300+ cities worldwide, it delivers sub-5ms cold starts and zero operational overhead. Compared to traditional server-based APIs, you get global low latency with no infrastructure management required.

Loading diagram...

Environment Setup — From Project Creation to Wrangler Configuration

bash
# Create project (select cloudflare-workers template)
npm create hono@latest my-edge-api
# Template: cloudflare-workers

cd my-edge-api
npm install

# Install Wrangler CLI globally (if not already installed)
npm install -g wrangler

# Login to Cloudflare account
wrangler login

# Start local dev server
npm run dev
# => http://localhost:8787

wrangler.toml Configuration — Defining Bindings

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

# KV Namespace binding
[[kv_namespaces]]
binding = "CACHE"
id = "your-kv-namespace-id"

# D1 Database binding
[[d1_databases]]
binding = "DB"
database_name = "my-database"
database_id = "your-d1-database-id"

# R2 Bucket binding
[[r2_buckets]]
binding = "STORAGE"
bucket_name = "my-bucket"

# Workers AI binding
[ai]
binding = "AI"

# Environment variables
[vars]
ENVIRONMENT = "production"

Bindings Type Definitions — Type-Safe Implementation with TypeScript

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

// Define Bindings types
type Bindings = {
  DB: D1Database
  CACHE: KVNamespace
  STORAGE: R2Bucket
  AI: Ai
  ENVIRONMENT: string
}

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

app.get('/', (c) => {
  // Full type inference on c.env.DB etc.
  const env = c.env.ENVIRONMENT
  return c.json({ env, message: 'Hello from the edge!' })
})

export default app

D1 Database Integration — Full CRUD Implementation

ts
// D1 CRUD operations
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 Integration — Cache Implementation

ts
// KV-based cache implementation
app.get('/api/products/:id', async (c) => {
  const id = c.req.param('id')
  const cacheKey = `product:${id}`

  // Try cache first
  const cached = await c.env.CACHE.get(cacheKey, 'json')
  if (cached) {
    c.header('X-Cache', 'HIT')
    return c.json(cached)
  }

  // Fetch from DB and cache the result
  const product = await c.env.DB
    .prepare('SELECT * FROM products WHERE id = ?')
    .bind(id)
    .first()

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

  // Cache for 3600 seconds
  await c.env.CACHE.put(cacheKey, JSON.stringify(product), {
    expirationTtl: 3600
  })

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

R2 Storage Integration — File Upload and Download

ts
// R2 file upload
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 file download
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 Integration — LLM, Image Generation, Embeddings

ts
// LLM text generation
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 })
})

// Text embeddings (for RAG, etc.)
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 — Scheduled Job Implementation

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

type Bindings = { DB: D1Database }

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

// Regular HTTP handler
app.get('/', (c) => c.text('API is running'))

// Cron Trigger handler (scheduled export)
export default {
  fetch: app.fetch,
  async scheduled(event: ScheduledEvent, env: Bindings) {
    console.log('Cron triggered:', event.cron)
    // Delete old data daily at midnight
    if (event.cron === '0 0 * * *') {
      await env.DB
        .prepare('DELETE FROM logs WHERE created_at < datetime("now", "-30 days")')
        .run()
    }
  }
}

Add the Cron schedule to `wrangler.toml`:

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

Deployment — Pushing to Production

bash
# Deploy to production
npx wrangler deploy

# Deploy to a specific environment
npx wrangler deploy --env production

# Custom domain (add to wrangler.toml)
# [[routes]]
# pattern = "api.example.com/*"
# zone_name = "example.com"

# Verify after deployment
npx wrangler tail  # Live log stream

Local Development — Remote vs Local Mode

bash
# Local mode (uses local D1/KV simulators)
npx wrangler dev

# Remote mode (connects to production D1/KV)
npx wrangler dev --remote

# Specify port
npx wrangler dev --port 9000

Initialize D1 locally:

bash
# Create migration file
npx wrangler d1 migrations create my-database create-users

# Apply to local D1
npx wrangler d1 migrations apply my-database --local

# Apply to production D1
npx wrangler d1 migrations apply my-database

Secret Management — Using wrangler secret put

bash
# Set a secret (prompted to enter value)
npx wrangler secret put JWT_SECRET
npx wrangler secret put DATABASE_URL

# List all secrets
npx wrangler secret list

# Delete a secret
npx wrangler secret delete JWT_SECRET

Add secrets to your Bindings type definition:

ts
type Bindings = {
  DB: D1Database
  JWT_SECRET: string  // Set via wrangler secret put
}

Monitoring and Logging — wrangler tail and Cloudflare Analytics

bash
# Real-time log stream
npx wrangler tail

# Filter to errors only
npx wrangler tail --status error

# Output in JSON format
npx wrangler tail --format json

The Cloudflare Dashboard Workers section provides real-time visibility into request counts, error rates, CPU time, and edge latency.

Pricing — Free Tier and Paid Plans

Cloudflare Workers offers a generous free tier that makes small projects viable at zero cost.

PlanPriceRequestsCPU Time
Free$0/month100K requests/day10ms/request
Workers Paid$5 USD/month10M requests/month30ms/request
Workers Paid (overage)$0.15 per 1MAdditional billingAdditional billing
Workers UnboundUsage-basedUnlimitedUp to 30s/request

KV, D1, and R2 each have their own free tiers and pay-as-you-go pricing.

Production Best Practices — Error Handling, Rate Limiting, CORS

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 }>()

// Security headers and CORS
app.use('*', cors({ origin: ['https://yourdomain.com'] }))
app.use('*', logger())

// Global error handling
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 handling
app.notFound((c) => c.json({ error: 'Not Found' }, 404))

// Simple rate limiting (KV-based)
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

Frequently Asked Questions (FAQ)

Q1. Is the CPU time limit a problem for Cloudflare Workers? The free plan limits CPU time to 10ms and the paid plan to 30ms. Design your APIs to be I/O-bound (DB and KV operations) rather than CPU-intensive. The Workers Unbound plan extends this to up to 30 seconds per request. Q2. Can I use native Node.js modules like fs or crypto? `fs` is not available, but `crypto` is accessible as the Web Crypto API. Enabling the `nodejs_compat` compatibility flag allows many Node.js modules to work. Q3. Does Cloudflare Workers support WebSockets? Yes. Cloudflare supports WebSocket connections, and Hono provides an `upgradeWebSocket` helper. Combined with Durable Objects, you can build real-time applications. Q4. How much data can D1 handle? Up to 10 GB per database as of 2026. For larger datasets, connecting to external PostgreSQL or MySQL via Cloudflare Hyperdrive is recommended. Q5. Can I migrate an existing Express app to Hono on Cloudflare Workers? Yes, migration is relatively straightforward due to the similar API design philosophy. Node.js-specific modules (path, fs, etc.) need replacing. A phased approach — first migrate to Hono using `@hono/node-server`, then move to Workers — is recommended. Q6. Can I use Prisma with Cloudflare Workers? Prisma supports D1 via `@prisma/adapter-d1`. However, for startup time reasons, the lighter Drizzle ORM is generally preferred.

Accelerate Your Hono and Cloudflare Workers Development with Oflight

Oflight provides design, development, and operational support for edge APIs built on Hono and Cloudflare Workers. From building serverless backends with D1, KV, R2, and Workers AI to migrating existing infrastructure to the edge, we cover it all. Initial consulting engagements start from $3,000 USD. Reach out for a free consultation. Explore our Software Development Services

Feel free to contact us

Contact Us