Middleware
A directory can hold a _middleware.ts. It applies to that directory and everything under it. Pipelines run outermost first, then the route handler. Each function gets the request args — request, response, ctx, params, query, body, session (not signals/stream/db/logger) — and can throw to stop the request.
This guard blocks the route unless the URL carries ?key=open — edit the URL in the demo to get through:
import { HttpError } from 'camelon';
import type { MiddlewareFunction } from 'camelon';
const requireKey: MiddlewareFunction = ({ query }) => {
if (query.key !== 'open') {
throw new HttpError(403, 'Blocked by middleware — add ?key=open to the URL');
}
};
export const middleware = [requireKey];export default function Guard() {
return (
<div class="d-card">
<strong>You're past the guard ✅</strong>
<p>
The <code>_middleware.ts</code> in this folder ran first and let you through because the URL
carried <code>?key=open</code>.
</p>
</div>
);
}A real auth guard reads the session instead:
import { redirect } from 'camelon';
import type { MiddlewareFunction } from 'camelon';
const requireAuth: MiddlewareFunction = ({ session }) => {
if (!session.has('userId')) throw redirect('/login');
};
export const middleware = [requireAuth];export default function Dashboard() {
return <h1>Welcome back</h1>;
}The _middleware.ts guard runs before Dashboard.tsx. If the session is empty it throws redirect, and the page never renders.
Guarding
Throw to stop the request before the handler runs:
throw redirect('/login');
throw notFound();
Passing data down
A value returned from middleware merges into ctx, the per-request scratch shared with the handler. It replaces Express's res.locals.
An app types itself in one place — the augmentable Register interface. Declare ctx to type the scratch, and session to key session.get/set:
// _middleware.ts
declare module 'camelon' {
interface Register {
ctx: { tenant: string };
session: { userId: string };
}
}
export const middleware: MiddlewareFunction[] = [
({ request }) => ({ tenant: tenantFrom(request) }), // → ctx.tenant
];
export type GetOutput = { userId: string | undefined; tenant: string };
export const get: GetFunction = ({ session, ctx }): GetOutput => {
return { userId: session.get('userId'), tenant: ctx.tenant };
};
Read the session from session (get('userId') is typed from Register['session']). Read middleware values from ctx.