Routing and middleware

Write request context middleware

Create one typed middleware that validates an incoming request id or generates a safe one.

Good custom middleware does one job, takes explicit inputs, and stores a typed result on context. No hidden globals.

This middleware accepts a bounded correlation id or creates one, stores it, and echoes it in the response:

import { randomUUID } from 'node:crypto'

const REQUEST_ID_PATTERN = /^[a-z0-9-]{8,64}$/i

const requestId = async (c, next) => {
  const incoming = c.req.header('x-request-id') ?? ''
  const id = REQUEST_ID_PATTERN.test(incoming) ? incoming : randomUUID()
  c.set('requestId', id)
  await next()
  c.header('x-request-id', id)
}

app.use('*', requestId)

app.get('/bookmarks', (c) => {
  console.log('request', c.get('requestId'))
  return c.json([])
})

Send a valid id and read it back:

curl -s -D - http://localhost:3000/bookmarks \
  -H 'x-request-id: trace-abc-123' -o /dev/null

Response headers should include x-request-id: trace-abc-123. Omit the header and curl still succeeds, but the value becomes a generated UUID in the response.

Type the variable when you create the app:

const app = new Hono<{ Variables: { requestId: string } }>()

Handlers and onError can read the same id without reparsing headers.

A realistic failure: you register app.use('*', requestId) after app.get('/bookmarks', ...). The route runs without an id in context or response headers. Fix by moving all app.use calls above route registration.

Another failure: accepting a 200-character x-request-id verbatim bloats logs. Send junk:

curl -s -D - http://localhost:3000/bookmarks \
  -H "x-request-id: $(python3 -c 'print("x"*200)')" -o /dev/null

The middleware should replace it with a UUID. Vitest should assert res.headers.get('x-request-id').length <= 64.

On Workers, swap node:crypto for crypto.randomUUID(). The middleware shape stays the same.

Wire the id into onError so 500 responses include the same value clients see in headers. Support staff can match curl output to logs without guessing.

Add a Vitest case that calls app.request('/bookmarks') without x-request-id and asserts the response header matches a UUID pattern. That catches regressions when middleware order breaks.

Oversized ids should never reach your log aggregator. When the middleware replaces junk with a UUID, logs and response headers must still match.

Register request-id middleware before auth, logging, and routes so every layer shares the same correlation value.

Copy the id into error JSON on 500 responses so curl users can quote it when they open a support ticket.

Try this on your own project: add request-id middleware and wire the same id into your error handler so production traces stay correlated.

Lesson completed