Skip to main content

Hook System

BurgerAPI uses a hook-based request lifecycle. Six named stages with clear jobs, each returning a defined shape.

Hooks control when code runs on a request.
Plugins extend the application (and may register hooks). Keep them separate.

Six hooks​

HookWhen
onRequestRequest enters the app
transformAfter routing, before validation (context decoration)
beforeRouteAfter validation, before the handler
afterRouteAfter the handler returns
mapResponseBefore the response is sent (final headers)
onErrorAny error in the lifecycle
onRequest → Routing → transform → Validation → beforeRoute
→ Handler → afterRoute → mapResponse

Errors thrown after routing (validation, beforeRoute, handler, afterRoute, mapResponse) jump to onError. Errors thrown in onRequest also run through the global, plugin and framework onError hooks. A request that matches no route is rendered directly (404) without running onError.

Scopes​

  1. Framework
  2. Plugin
  3. Global (src/hooks.ts)
  4. Route (api/**/hooks.ts)

Request hooks run Framework → Plugin → Global → Route. Response hooks (afterRoute, mapResponse) and error hooks (onError) run nearest-first: Route → Global → Plugin → Framework.

There is no folder or group inheritance of hooks. Each route directory is self-contained.

Route convention files​

Alongside route.ts:

  • hooks.ts : route hooks
  • schema.ts : validation
  • openapi.ts : OpenAPI
  • config.ts : route options (auth, cache, timeout, …)

Types for this feature​

Each hook point has its own type. TypeScript checks the return value of your hook against that type.

The types you use (all from burger-api):

  • ForwardHook: for onRequest and beforeRoute: returns Response (stop), (response) => Response (transform the eventual response), or undefined (continue)
  • ResponseHook: for afterRoute and mapResponse: returns Response, (response) => Response, or undefined
  • ErrorHook: for onError: takes (error, ctx), returns Response or undefined
  • RouteHooks: the object with all hook points, for typing hooks.ts files

✅ Correct: each hook returns something from its contract:

// hooks.ts
import type { RouteHooks } from "burger-api";

export const beforeRoute: RouteHooks["beforeRoute"] = [
(ctx) => new Response("blocked", { status: 401 }), // stop
() => (res) => res, // continue, but transform the final response
() => undefined, // continue
];

export const afterRoute: RouteHooks["afterRoute"] = [
(ctx) => (res) => res, // change the response
];

❌ Wrong: a hook returns a value that is not in the contract:

export const beforeRoute: RouteHooks["beforeRoute"] = [
() => 42, // ❌ Type 'number' is not assignable
];

The full rules for each return value are on the Hook Return Types page. See the TypeScript overview.

When a route's hooks read ctx.validated, defineHooks(schema, hooks) types every hook's ctx from the route's schema in one step. See Route Hooks.

Check your code: bun run typecheck.