Askr
Server & APIs

Context and Responses

Use context and responses with the current published Askr packages.

  • @askrjs/askr0.0.59

How to use context and responses

Read dependencies and request state from the handler context and return a response helper instead of mutating a global response object.

  1. Import the published @askrjs/askr entrypoint.
  2. Keep context and responses configuration next to the component, route, or server composition root that owns it.
  3. Handle pending, unavailable, cancellation, and failure states, then verify the production path.
router.get('/projects/:id', async (context) => {
  const project = await context.dependencies.projects.get(context.params.id);
  return project ? ok(project) : notFound();
});

Server context

ServerContext<RouteParams> is the one object every handler and middleware sees: request, url, params, headers, query, state, auth, and signal cover the inbound side, while sse(), bind(), and the response helpers cover the outbound side. params is typed per-route from the path pattern, and auth is an AuthContext resolved once per request by whatever AuthResolver the app was configured with. signal is an AbortSignal tied to the request's lifetime, so long-running work — a database call, an SSE loop — can watch it and stop early once the client disconnects.

Request state

ctx.state is a plain Record<string, unknown> that exists purely for middleware to pass data forward to the next middleware or the final handler — request-id middleware, for example, might stash a generated ID there. It has no built-in shape or validation; whatever one middleware writes to it, a later stage reads back with its own type assumptions. Because state lives on the context object for the duration of a single request, it never leaks between requests the way a module-level variable would.

Response helpers

Beyond the status-named helpers, ctx.json(value, init) and ctx.text(value, init) build responses with the right Content-Type header already set, and ctx.redirect(location, status?) defaults to 302 but also accepts 301, 303, 307, or 308 explicitly. Every helper takes an optional standard ResponseInit as its last argument, so a custom header or status override doesn't mean giving up the helper. These are plain functions under the hood — exported standalone as json, text, redirect, ok, and so on — so they're callable from code that never touches a ServerContext, such as a WebSocket adapter's upgrade rejection path.

Problem details

ctx.problem(status, detail, options) produces an RFC 7807-shaped application/problem+json body — type, title, status, and an optional detail and instance — with anything extra folded in through options.extensions. The convenience helpers like notFound() and unprocessableEntity() are thin wrappers around the same machinery with sensible type and title defaults already filled in. The binding layer uses this format automatically: invalid JSON, a non-object JSON root, malformed multipart data, or reading an already-consumed body all produce a 400 application/problem+json response without any handler code involved.