Invocation Context

Use typed context keys to share invocation-scoped state across middleware and handlers.

Overview

fest attaches a InvocationContext to each ServerRequest. The context is designed for invocation-local state such as:

  • request ids
  • authenticated user data
  • feature flags
  • cached values derived during middleware

Creating Context Keys

Use createContextKey() to create a typed key.

import { createContextKey } from "@hornjs/fest";

const requestIdKey = createContextKey<string>("unknown");
const userKey = createContextKey<{ id: string }>();

If you pass a default value, context.get() can always return a value for that key.

Reading and Writing Values

import { createContextKey, serve } from "@hornjs/fest";
import { NodeRuntimeAdapter } from "@hornjs/fest/node";

const requestIdKey = createContextKey<string>("unknown");

serve({
  adapter: new NodeRuntimeAdapter(),
  middleware: [
    async (request, next) => {
      request.context.set(requestIdKey, crypto.randomUUID());
      return next(request);
    },
  ],
  fetch(request) {
    return Response.json({
      requestId: request.context.get(requestIdKey),
    });
  },
});

InvocationContext

The context object supports three core operations:

context.get(key)

Read a value by typed key.

  • returns the stored value when present
  • returns the key's default value when available
  • throws if the key has no stored value and no default value

context.set(key, value)

Store a value for the current request.

context.has(key)

Check whether a value has been explicitly stored for the current request.

This is useful when the key also has a default value and you need to know whether middleware has actually set it yet.

Default Value Behavior

const localeKey = createContextKey("en");

request.context.get(localeKey); // "en"
request.context.has(localeKey); // false

request.context.set(localeKey, "zh-CN");
request.context.get(localeKey); // "zh-CN"
request.context.has(localeKey); // true

If a key has no default value, calling get() before set() throws.

Why Typed Keys Instead of Strings

Typed keys avoid accidental collisions between unrelated features.

Two modules can both define a "user"-like concept without sharing the same storage slot, because identity is based on the context key object, not a string name.

Relationship to Middleware

Invocation context is most useful in middleware chains:

  • early middleware derives shared state
  • later middleware consumes or updates it
  • the final handler reads the resolved value

This keeps request-specific data off global variables and avoids ad-hoc request object mutation.