Middleware
Middleware may fill req.data and short-circuit. Contracts make ordering compile-time safe.
Contract
Middleware<TAdds, TNeeds> - TAdds is written into req.data; TNeeds must already be present from earlier middleware.
import { HttpServer, type Middleware } from "jsr:@webtools/expressapi";
type User = { id: string; name: string; role: "admin" | "user" };
const auth: Middleware<{ user: User }> = (req, res) => {
const token = req.headers.get("authorization");
if (!token) return res.status(401).json({ error: "Unauthorized" });
req.data.user = { id: "1", name: "Alice", role: "admin" };
};
const requireAdmin: Middleware<{ admin: boolean }, { user: User }> = (req) => {
req.data.admin = req.data.user.role === "admin";
};
const server = new HttpServer()
.use(auth)
.use(requireAdmin)
.get("/me", (req, res) =>
res.json({ name: req.data.user.name, admin: req.data.admin }));Order is enforced by the compiler. Registering requireAdmin without auth before it fails with a clear error.
Registration order (runtime matches types)
.use(middleware) applies to routes registered after that call. When a route is added, the router snapshots the middlewares registered so far onto the route. Routes registered earlier are unaffected.
const server = new HttpServer()
.post("/login", (_req, res) => res.json({ token: "..." })) // public - no auth
.use(auth)
.get("/profile", (req, res) => res.json({ id: req.data.user.id })); // auth runsPer-route middleware
The third argument of a route method takes middlewares that run only for that route, after the snapshotted .use() chain. Use this to add extra gates (for example admin-only) without applying them to every subsequent route:
const server = new HttpServer()
.post("/login", (_req, res) => res.json({ token: "..." }))
.use(auth)
.get("/profile", (req, res) => res.json({ id: req.data.user.id }))
.get("/admin", (req, res) => res.json({ admin: req.data.admin }), [requireAdmin]);Within the per-route array, TAdds / TNeeds chain exactly like successive .use() calls. Wrong order fails at compile time.
Inline helper
Annotating a named constant is the usual way. Use the middleware() helper for an inline middleware - TAdds cannot be inferred from the function body:
import { HttpServer, middleware } from "jsr:@webtools/expressapi";
const server = new HttpServer()
.use(middleware<{ requestId: string }>((req) => {
req.data.requestId = crypto.randomUUID();
}))
.get("/", (req, res) => res.json({ id: req.data.requestId }));Execution order
- CORS headers
- Route params extraction
- Schema validation (
query,params,body) - Route middleware chain (
.use()middlewares registered before this route, then the per-route array) - Route handler
Returning a response from any step stops the chain. Returning nothing continues. Full auth walkthrough: Auth with JsonToken.