Routing
Express-shaped methods, nested routers, and consistent URL matching.
HTTP methods
get, post, put, patch, delete share the signature (url, handler, middlewares?, schemas?).
const server = new HttpServer()
.get("/users", (_req, res) => res.json({ users: [] }))
.post("/users", (_req, res) => res.status(201).json({ created: true }))
.put("/users/:id", (req, res) => res.json({ updated: true, id: req.params.id }))
.patch("/users/:id", (req, res) => res.json({ patched: true, id: req.params.id }))
.delete("/users/:id", (_req, res) => res.status(204).send(null));Query parameters
Parsed automatically. Without a schema, values are string | undefined. With a schema, they are validated and typed - see Validation.
Modular routers
Router<TData> groups routes and declares the req.data context it expects from the parent. Mount with a prefix; prefixes combine at the type level too.
export const usersRouter = new Router<{ user: User }>()
.get("/users", (_req, res) => res.json({ users: [] }))
.get("/users/:id", (req, res) =>
res.json({ id: req.params.id, by: req.data.user.id }));
const server = new HttpServer()
.use(auth)
.use("/api", usersRouter);Mounting is checked at compile time against new Router<TData>(). Middlewares the child registers with its own .use() do not become parent requirements.
URL normalization
Consecutive slashes collapse; trailing slashes are removed (except /). /users, /users/, and //users match the same route.