Cloudflare Workers essentials - Hono routing, middleware, request handling, bindings. Trigger: When working with Cloudflare Workers, Hono framework, serverless APIs, edge computing.
import { Hono } from "hono"
type Env = {
DB: D1Database
MY_BUCKET: R2Bucket
API_KEY: string
}
const app = new Hono<{ Bindings: Env }>()
app.get("/", (c) => c.text("Hello!"))
app.get("/api/users", async (c) => {
const users = await c.env.DB.prepare("SELECT * FROM users").all()
return c.json(users.results)
})
export default app
// GET, POST, PUT, DELETE
app.get("/users", (c) => c.json({ users: [] }))
app.post("/users", async (c) => {
const body = await c.req.json()
return c.json({ created: true }, 201)
})
// Dynamic params
app.get("/users/:id", (c) => {
const id = c.req.param("id")
return c.json({ id })
})
// Multiple params
app.get("/posts/:postId/comments/:commentId", (c) => {
const postId = c.req.param("postId")
const commentId = c.req.param("commentId")
return c.json({ postId, commentId })
})
// Query params
app.get("/search", (c) => {
const query = c.req.query("q")
const page = c.req.query("page") || "1"
return c.json({ query, page })
})
app.post("/api/data", async (c) => {
// JSON body
const body = await c.req.json()
// Form data
const formData = await c.req.formData()
const file = formData.get("file")
// Headers
const auth = c.req.header("Authorization")
// Cookies
const sessionId = c.req.cookie("session_id")
return c.json({ success: true })
})
// JSON
app.get("/json", (c) => c.json({ message: "Hello" }, 200))
// Text
app.get("/text", (c) => c.text("Plain text"))
// HTML
app.get("/html", (c) => c.html("<h1>Hello</h1>"))
// Redirect
app.get("/redirect", (c) => c.redirect("/new-location", 301))
// Custom
app.get("/custom", (c) => {
return new Response("Custom", {
status: 200,
headers: { "X-Custom": "value" }
})
})
// Global logging
app.use('*', async (c, next) => {
console.log(`[${c.req.method}] ${c.req.url}`)
await next()
})
// Auth middleware
const authMiddleware = async (c, next) => {
const token = c.req.header("Authorization")
if (!token || !token.startsWith("Bearer ")) {
return c.json({ error: "Unauthorized" }, 401)
}
const user = await verifyToken(token.substring(7))
c.set("user", user) // Store in context
await next()
}
// Apply to routes
app.use('/api/*', authMiddleware)
// Access in route
app.get('/api/profile', (c) => {
const user = c.get("user")
return c.json({ user })
})
// CORS
import { cors } from "hono/cors"
app.use('*', cors({
origin: ["https://yourapp.com"],
allowMethods: ["GET", "POST", "PUT", "DELETE"],
credentials: true
}))
// Error handler
app.onError((err, c) => {
console.error(err)
return c.json({ error: "Internal Error" }, 500)
})
// 404 handler
app.notFound((c) => c.json({ error: "Not Found" }, 404))
const api = new Hono()
api.get("/users", (c) => c.json({ users: [] }))
api.post("/users", (c) => c.json({ created: true }))
api.get("/users/:id", (c) => c.json({ id: c.req.param("id") }))
// Mount group
app.route("/api", api)
// Routes: /api/users, /api/users/:id
app.post("/analytics", async (c) => {
const event = await c.req.json()
// Run in background (non-blocking)
c.executionCtx.waitUntil(
fetch("https://analytics.example.com/track", {
method: "POST",
body: JSON.stringify(event)
})
)
return c.json({ success: true })
})
app.get("/proxy", async (c) => {
const response = await fetch("https://api.example.com", {
method: "GET",
headers: {
"Authorization": `Bearer ${c.env.API_KEY}`,
"Content-Type": "application/json"
}
})
if (!response.ok) {
return c.json({ error: "Failed" }, response.status)
}
const data = await response.json()
return c.json(data)
})
import { Hono } from "hono"
import { cors } from "hono/cors"
const app = new Hono<{ Bindings: Env }>()
// Middleware
app.use('*', cors())
// Health check
app.get("/health", (c) => c.json({ status: "ok" }))
// API routes
const api = new Hono()
api.get("/users", async (c) => {
const users = await c.env.DB.prepare("SELECT * FROM users").all()
return c.json(users.results)
})
api.post("/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)
})
api.get("/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)
})
api.delete("/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({ success: true })
})
app.route("/api", api)
// Handlers
app.notFound((c) => c.json({ error: "Not Found" }, 404))
app.onError((err, c) => {
console.error(err)
return c.json({ error: "Internal Error" }, 500)
})
export default app
// ✅ Use waitUntil for non-blocking tasks
c.executionCtx.waitUntil(logAnalytics(event))
return c.json({ success: true })
// ❌ Don't await non-critical tasks
await logAnalytics(event) // Blocks response
return c.json({ success: true })
// ✅ Cache responses
app.get("/static-data", (c) => {
return c.json(data, 200, {
"Cache-Control": "public, max-age=3600"
})
})
// ✅ Stream large responses
app.get("/large-file", async (c) => {
const object = await c.env.BUCKET.get("large.json")
return new Response(object.body)
})
# Init worker
wrangler init my-worker
# Dev server
wrangler dev
# Deploy
wrangler deploy
# Tail logs
wrangler tail