Middleware¶
Middleware in Katal can intercept requests before they reach a controller and modify responses afterwards. Every middleware implements the Middleware interface:
interface Middleware {
before?(ctx: MiddlewareContext): Promise<Response | null>;
after?(ctx: MiddlewareContext, response: Response): Promise<Response>;
}
before— Return aResponseto short-circuit the pipeline (e.g. 401), ornullto continue.after— Mutate or replace the response before it is sent (e.g. inject headers).
Global Middleware¶
Applied to every request:
import { Application } from "katal";
const app = new Application({ port: 3000 });
app.use({
async before(ctx) {
console.log(`[${new Date().toISOString()}] ${ctx.request.method} ${new URL(ctx.request.url).pathname}`);
return null; // allow request to continue
},
});
Named Middleware¶
Register a middleware under a string key and reference it on individual routes or route groups:
app.middleware("require-api-key", {
async before(ctx) {
const key = ctx.request.headers.get("X-Api-Key");
if (key !== process.env.API_KEY) {
return new Response(JSON.stringify({ message: "Forbidden" }), {
status: 403,
headers: { "Content-Type": "application/json" },
});
}
return null;
},
});
// Use on a route
router.get("/webhooks", WebhookController, { middleware: ["require-api-key"] });
// Use on a group
router.group("/admin", (r) => {
r.get("/dashboard", DashboardController);
r.delete("/users/:id", DeleteUserController);
}, { middleware: ["auth", "can:admin"] });
Middleware Execution Order¶
- Global middleware (in registration order)
- Route / group middleware (in array order)
- Controller
beforeHandle()hook - Controller
handle() - Controller
afterHandle()hook - Middleware
after()hooks (reverse order)
CORS Middleware¶
Enable CORS for browser clients:
import { createCorsMiddleware } from "katal";
app.use(createCorsMiddleware({
origin: ["https://app.example.com", "https://www.example.com"],
methods: ["GET", "POST", "PUT", "DELETE"],
allowedHeaders: ["Content-Type", "Authorization"],
credentials: true,
}));
CorsOptions¶
| Option | Type | Default | Description |
|---|---|---|---|
origin |
string \| string[] |
"*" |
Allowed origin(s) |
methods |
string[] |
common verbs | Allowed HTTP methods |
allowedHeaders |
string[] |
["Content-Type","Authorization"] |
Allowed request headers |
exposedHeaders |
string[] |
[] |
Headers exposed to the browser |
credentials |
boolean |
false |
Include Access-Control-Allow-Credentials |
maxAge |
number |
86400 |
Preflight cache duration in seconds |
Security note: Avoid
origin: "*"combined withcredentials: true. Pick one.
Rate Limit Middleware¶
Throttle requests per client. See Rate Limiting for the full reference.
import { createRateLimitMiddleware } from "katal";
app.use(createRateLimitMiddleware({
windowMs: 60_000, // 1 minute
maxRequests: 100,
}));
Auth Middleware¶
Authenticate JWT tokens. See Authentication.
app.middleware("auth", createAuthMiddleware(auth));
app.middleware("auth:optional", createOptionalAuthMiddleware(auth));
Custom State Middleware¶
Use ctx.state to pass data between middleware and controllers:
app.middleware("tenant", {
async before(ctx) {
const tenantId = ctx.request.headers.get("X-Tenant-Id");
if (!tenantId) return new Response("Missing tenant", { status: 400 });
ctx.state.tenant = await db.getTenant(tenantId);
return null;
},
});
// In controller:
const tenant = ctx.state.tenant as Tenant;
Timing / Observability Middleware¶
Record latency with before + after:
app.use({
async before(ctx) {
ctx.state.startedAt = Date.now();
return null;
},
async after(ctx, response) {
const ms = Date.now() - (ctx.state.startedAt as number);
const headers = new Headers(response.headers);
headers.set("X-Response-Time", `${ms}ms`);
return new Response(response.body, { status: response.status, headers });
},
});