CRUMB a card from devarno-cloud

Hono BFF Middleware Stack

so1 intermediate 5 min read

ELI5

Every BFF request walks down an assembly line: a tag-and-stamp station (requestId), an ID check (auth), a recorder (logging), the workstation (route), and a quality inspector (error handler) that wraps any defects in a standard envelope before shipping.

Technical Deep Dive

Pipeline Order

flowchart LR
inb[inbound HTTP] --> rid[requestId]
rid --> auth[Clerk/BetterAuth verify]
auth --> log[OTEL structured log]
log --> route[route handler]
route --> err[error handler]
err --> outb[outbound HTTP]
err -. catches throws .- route

Per ADR-002, middleware runs before any route. The error handler is registered last but logically wraps everything: throws inside the route surface as ErrorEnvelope JSON with the correct HTTP status (see so1-011).

Responsibilities

StageResponsibility
requestIdGenerate UUID v4, attach to request context, set X-Request-Id response header
authRead Authorization: Bearer <token>, verify with auth backend, attach { userId, orgId } to context
logStructured log per request: timestamp, requestId, method, path, status, duration_ms, userId, orgId, error
routeBusiness logic, calls adapters (so1-005)
errorCatch unhandled throws, redact, wrap in ErrorEnvelope, set status

Route Surface (per ADR-002)

/api/auth/*, /api/catalog/*, /api/workflows/*, /api/jobs/*, /api/mcp/*.

Why Hono

Per ADR-002: ~14KB gzipped, compiled middleware, edge-ready (Cloudflare Workers, Deno, Node), TypeScript-first. The pipeline shape is a deliberate Express/Fastify analogue.

Key Terms

  • Middleware → function that wraps (c, next) and may run code before, after, or instead of next().
  • Context → Hono’s per-request bag where middleware writes userId, requestId, etc.
  • Adapter → integration-specific module called from a route (see so1-005).

Q&A

Q: If auth fails, do logging and the route still run? A: No. Auth middleware short-circuits with 401 before logging the success path; the error handler still wraps the response and the request is logged with status: 401 and error.code: UNAUTHORIZED.

Q: What guarantees requestId exists for the error handler? A: requestId is the first middleware. Every later stage, including thrown errors, has it in context.

Q: Where are validation errors raised? A: ADR-002 says “contract validation (Zod middleware)” sits between auth and the route; failures throw VALIDATION_ERROR (400) and surface through the error handler.

Examples

A POST /api/jobs with an expired session: requestId stamped, auth verifies → throws 401, error handler emits { requestId, error: { code: "UNAUTHORIZED", message: "…" } }, log line shows status: 401, duration_ms: 3, userId: null.

neighbors on the map