Routing and configuration

Reuse Hono routes

Bring the web-standard routing and validation patterns from the Web APIs course into a Cloudflare Worker entry point.

Hand-rolling if (url.pathname === ...) chains gets old after three routes. We need a router, and Hono is the one I use on Workers. Its core is built on Request and Response, not on Node’s http module, so it runs on Workers with no adapter.

Install it and replace the fetch handler with a Hono app:

npm install hono
import { Hono } from 'hono'

const app = new Hono<{ Bindings: Env }>()
app.get('/api/health', c => c.json({ ok: true }))
export default app

Exporting the app works because a Hono instance has a fetch method with the exact signature Workers expect. The { Bindings: Env } generic tells Hono about your environment, so c.env.DB is typed later.

Restart npm run dev and curl -i http://localhost:8787/api/health still returns 200 and {"ok":true}. Same behavior, much less code to grow.

Hono owns HTTP, adapters own storage

Here is the boundary I keep in every Worker project. Hono handles route matching, middleware, validation errors, and response formatting. Small repository adapters handle D1 or KV calls. Nothing in between.

The payoff is testability. Your validation functions and problem-response helpers live in plain modules with no Cloudflare imports. They run in any test runner in milliseconds. Only the adapters need the real bindings, and we test those inside the Workers runtime in the last module.

Middleware order is behavior

Middleware runs in the order you register it, and that order is visible to clients. Put the request ID and error handler first so they wrap everything. Authenticate before the protected routes, not after. And make sure the final 404 still returns your JSON error shape:

app.notFound(c => c.json({ error: 'Not found' }, 404))
app.onError((err, c) => {
  console.error(err.message)
  return c.json({ error: 'Internal error' }, 500)
})

Without notFound, Hono answers unknown paths with a plain-text 404 Not Found. Your API clients expect JSON, so set it explicitly.

Don’t swallow errors

A pattern I see a lot: wrap every route in try/catch and return 200 with { error: '...' }. Don’t. Clients and monitoring both read the status code. A 500 from onError, logged once with safe context, tells the truth. A 200 hides it.

Now bring over the Books API routes from the Web APIs course. Add /api/links and /api/links/:id as placeholders that return the same statuses and error shapes you used there: 200 for a list, 201 for a create, 404 for a missing link, 400 for invalid input. We connect them to real storage in the next module.

Lesson completed