Request lifecycle

A Beignet request moves through a predictable pipeline before your application code runs and before a response is sent.

HTTP request
  -> adapter request normalization
  -> route matching
  -> request parsing
  -> contract request validation
  -> context creation
  -> beforeHandle hooks
  -> route handler or use case
  -> route-owned response validation
  -> beforeSend hooks
  -> adapter response serialization
  -> afterSend hooks

This pipeline keeps business responses strict without forcing every route to declare infrastructure responses such as malformed JSON, auth failures, rate limits, unmatched routes, or unexpected failures.

Route matching

Beignet apps register routes centrally in server/index.ts with defineRoutes. A catch-all app/api/[[...path]]/route.ts file exposes server.api, so the adapter can dispatch the incoming request to the registered contract and handler.

If no route matches, Beignet returns a framework-owned error response.

Request validation

The runtime parses path params, query params, headers, and JSON body data from the request. It validates each declared schema before your handler runs.

Invalid request data never reaches the handler. It becomes a framework-owned error response with a standard error envelope.

Context and hooks

createContext builds the per-request context used by handlers, hooks, and use cases. Typical context includes requestId, auth/session state, and ports.

Hooks run around the handler for application-level behavior:

Hooks can short-circuit the request. Short-circuit responses are framework-owned unless the hook returns a native Response.

Handler execution

Handlers receive validated inputs:

{
  req,
  ctx,
  path,
  query,
  body,
  headers,
}

In Beignet apps, handlers usually call a use case and return a response that matches the contract:

{
  contract: getProject,
  handle: async ({ ctx, path }) => ({
    status: 200,
    body: await getProjectUseCase.run({
      ctx,
      input: { id: path.id },
    }),
  }),
}

Response ownership

Beignet separates responses into three groups:

OwnerSourceValidation
Route-ownedHandler returns { status, body } or throws route-owned AppErrorValidated against contract.responses
Framework-ownedParsing, validation, hooks, unmatched routes, global error handling, contract violationsUses Beignet's standard error envelope
Transport-ownedNative Response returned by handler or hookBypasses JSON response validation

Use route-owned responses for business outcomes. Use framework-owned responses for infrastructure and lifecycle failures. Use native Response for redirects, downloads, streaming, Server-Sent Events, or other non-JSON transport behavior.

Client behavior

Typed clients use the same contracts. A non-2xx route-owned response is parsed against the declared error response schema. Framework-owned errors use Beignet's standard { code, message, details?, requestId? } envelope when the response includes Beignet's ownership header.

Use call() when failures should throw ContractError. Use safeCall() when explicit result branching is clearer.