Context
Morphis creates a fresh request context for every incoming request. You access it through the current variable.
That context is unique per request, so values you attach during one request do not leak into another request. This makes it the right place for request-scoped data such as userId, permissions, tenant information, resolved database connections, trace state, or any other values you want to carry through the request lifecycle.
What current is
Internally, Morphis uses async local storage and wraps each request in runWithContext() inside the router. In application code, that means you can read or write current from middleware, controllers, services, and model-adjacent logic during the same request.
import { current } from 'morphis';
current.userId = 123;
current.permissions = ['posts.read', 'posts.write'];Because the context is request-local, later code in the same request can read those values safely:
import { current } from 'morphis';
if (!current.permissions?.includes('posts.write')) {
throw new Error('Forbidden');
}current only works during a live request. Accessing it outside the request lifecycle throws an error.
Typical use cases
userIdset by authentication middlewarepermissionsor role data resolved from a token or session- tenant or workspace identifiers
- request tracing data such as
trackId - resolved database handles via
current.db
Built-in context fields
Morphis already uses the context for a few framework-level values:
current.trackIdis populated byTrackMiddlewarecurrent.pathis populated byLoggerMiddlewarecurrent.tracestores the active call tracecurrent.db[name]is populated byConnect(name)when a database connection is resolved
These are normal context fields. Your own application data can live next to them.
Add your own fields
At runtime, you can attach extra properties to the context at any point during the request lifecycle.
For type-safe editor support, extend the Context interface in your app.
If your project was created with the Morphis CLI, the usual place is src/types/Context.d.ts:
declare module 'morphis' {
interface Context {
userId?: number;
permissions?: string[];
}
}After that, current.userId and current.permissions are typed everywhere in your project.
Example: attach data in middleware
Authentication or authorization middleware is the most common place to enrich the context.
import { Middleware, current, type Request } from 'morphis';
export class AuthMiddleware extends Middleware {
async handler(req: Request, next: (req: Request) => Promise<unknown>) {
const user = await authenticate(req);
current.userId = user.id;
current.permissions = user.permissions;
return next(req);
}
}Later in a controller or service, those values are still available for the same request:
import { current } from 'morphis';
export class PostService {
async createPost(data: unknown) {
if (!current.permissions?.includes('posts.write')) {
throw new Error('Forbidden');
}
return {
createdBy: current.userId,
data,
};
}
}When to use setContextFactory()
Most projects only need module augmentation so TypeScript knows about custom fields.
Use setContextFactory() when you want each request to start from a custom Context subclass, usually because you need constructor logic or default values.
import { Context, setContextFactory } from 'morphis';
export class AppContext extends Context {
userId?: number;
permissions: string[] = [];
}
setContextFactory(() => new AppContext());If you need subclass-specific typing directly, use useContext():
import { useContext } from 'morphis';
import { AppContext } from './AppContext';
const ctx = useContext<AppContext>();
ctx.permissions.push('posts.read');Request lifecycle behavior
The key rule is simple: the context is created once per request, shared throughout that request, and discarded when the request finishes.
That means you are free to attach application-specific data during the lifecycle, and anything that reads current later in the same request sees the same request-local values.
This is why context is the right fit for values such as:
- authenticated user identity
- permission arrays
- tenant selection
- feature flags resolved for the current request
- request-local services or helpers