Skip to content

Context

Context is built once per request and passed to every guard and resolver for that request.

Defining context

ts
import { defineContext, getHeader } from '@capixjs/core';

const buildContext = defineContext(async (req) => ({
  requestId: crypto.randomUUID(),
  user:      await verifyToken(getHeader(req, 'authorization')),
  db,
  logger:    pino({ level: 'info' }),
}));

defineContext accepts a function (req: RawRequest) => Context | Promise<Context>. The RawRequest type is:

ts
type RawRequest = {
  headers: Record<string, string | string[] | undefined>;
};

Use getHeader(req, name) for type-safe access — it returns string | undefined regardless of whether the header is a string or array.

Typed factories

Once you have a context type, create typed capability factories:

ts
import { capability } from '@capixjs/core';

export type AppContext = Awaited<ReturnType<typeof buildContext>>;

export const cap     = capability.withContext<AppContext>();
export const authCap = capability.withContext<AppContext & { user: NonNullable<AppContext['user']> }>();

All capabilities created with cap or authCap will have ctx typed as AppContext.

Sync vs async

buildContext can be synchronous. The execution engine detects sync context builders and skips the async path for a small performance gain:

ts
// Sync — slightly faster hot path
const buildContext = defineContext((req) => ({
  requestId: crypto.randomUUID(),
  user:      headerToUser(req.headers.authorization),
}));

Context per transport

The same buildContext is called for every transport. For queue-originated jobs, headers will be minimal (no real Authorization). If you need per-transport context differences, check the headers defensively:

ts
const buildContext = defineContext(async (req) => {
  const serviceKey = getHeader(req, 'x-service-key');
  const user = serviceKey === process.env.QUEUE_SECRET
    ? SERVICE_ACCOUNT
    : await verifyJwt(getHeader(req, 'authorization'));
  return { requestId: crypto.randomUUID(), user, db };
});

Plugin context extension

Plugins can extend the context. The extended context is merged into every request's context:

ts
import { definePlugin } from '@capixjs/core';

const metricsPlugin = definePlugin({
  context: (base) => ({
    ...base,
    metrics: new MetricsClient(),
  }),
});

createServer({
  context: buildContext,
  plugins:  [metricsPlugin],
  ...
});

Plugin extensions are applied after buildContext and before guards run.

BaseContext

BaseContext is the minimum type all contexts must extend:

ts
type BaseContext = {
  requestId: string;
};

defineContext enforces this — your returned object must include requestId.

Released under the MIT License.