HTTP Routing
HTTP Routing at the Askr server boundary, with validated input and explicit failure states.
Example
Build the HTTP surface from a router and context-first handlers; bind input and return an explicit response helper from the same boundary.
import { createRouter } from '@askrjs/server/router';
export const router = createRouter().post('/projects', async (context) => {
const input = await context.bind<CreateProjectInput>();
const project = await projects.create(input);
return context.created(project);
});Create a router
For low-level HTTP applications, createRouter() from @askrjs/server/router is the canonical route source. It returns a Router with no routes registered yet. Router supports the HTTP and WebSocket registration methods — get, post, put, patch, delete, options, head, trace, connect, and ws — plus use(...middleware) and read-only routes and middleware arrays you can inspect after registration. Pass the finished router as the router field of createServerApp({ router, middleware, ...options }); register routes through the router rather than passing raw route arrays.
Define routes
Define a route module by exporting a createRouter() result and registering routes with its HTTP verb methods, including router.route(method, path, handler, options) for one or several methods and router.ws(path, handler, options) for upgrades. Pass that router to createServerApp({ router }). Share registration code through functions that accept or return a Router; route options hold auth and per-route middleware.
Path parameters
Route paths use {name} for a single segment and {*name} for a named wildcard that captures the rest of the path — the README's example is router.get("/objects/{*key}", ctx => ctx.ok({ key: ctx.params.key })). Because paths are typed as const string literals, PathParams<Path> extracts the parameter names at compile time, so ctx.params is fully typed inside the handler without a separately written params interface. Values in ctx.params are always decoded strings, and the binding model treats them as authoritative — they win over a same-named query or body value when ctx.bind() merges everything together.
Route composition
Router methods return the Router itself, so calls chain — router.use(requestId()).get(...).post(...) — and use(...middleware) can be called more than once to layer cross-cutting concerns before route-specific ones run. Because route registration is usually wrapped in a plain function, like the README's registerUserRoutes(router, deps), a large API gets built by writing one function per feature area and calling each with the same router and a narrowed slice of dependencies. Keep defineRoutes() and standalone routes arrays for lower-level compatibility or tests; they are not peer recommendations for application composition.