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.
Environment Setup — From Project Creation to Wrangler Configuration
# 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:8787wrangler.toml Configuration — Defining Bindings
# 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
// 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 appD1 Database Integration — Full CRUD Implementation
// 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
// 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
// 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
// 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
// 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`:
[[triggers.crons]]
crons = ["0 0 * * *"]Deployment — Pushing to Production
# 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 streamLocal Development — Remote vs Local Mode
# 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 9000Initialize D1 locally:
# 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-databaseSecret Management — Using wrangler secret put
# 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_SECRETAdd secrets to your Bindings type definition:
type Bindings = {
DB: D1Database
JWT_SECRET: string // Set via wrangler secret put
}Monitoring and Logging — wrangler tail and Cloudflare Analytics
# Real-time log stream
npx wrangler tail
# Filter to errors only
npx wrangler tail --status error
# Output in JSON format
npx wrangler tail --format jsonThe 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.
| Plan | Price | Requests | CPU Time |
|---|---|---|---|
| Free | $0/month | 100K requests/day | 10ms/request |
| Workers Paid | $5 USD/month | 10M requests/month | 30ms/request |
| Workers Paid (overage) | $0.15 per 1M | Additional billing | Additional billing |
| Workers Unbound | Usage-based | Unlimited | Up 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
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 appFrequently 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