Invocation Context
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.