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 runs

Per-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

  1. CORS headers
  2. Route params extraction
  3. Schema validation (query, params, body)
  4. Route middleware chain (.use() middlewares registered before this route, then the per-route array)
  5. Route handler

Returning a response from any step stops the chain. Returning nothing continues. Full auth walkthrough: Auth with JsonToken.