Askr
Server & APIs

HTTP Routing

Use http routing with the current published Askr packages.

  • @askrjs/server/router0.0.3

How to use http routing

Build the HTTP surface from a router and context-first handlers; bind input and return an explicit response helper from the same boundary.

  1. Import the published @askrjs/server/router entrypoint.
  2. Keep http routing configuration next to the component, route, or server composition root that owns it.
  3. Handle pending, unavailable, cancellation, and failure states, then verify the production path.
import { createRouter, created } from '@askrjs/server';

export const router = createRouter().post('/projects', async (context) => {
  const input = await context.bind<CreateProjectInput>();
  const project = await context.dependencies.projects.create(input);
  return created(project);
});

Create a router

createRouter() from @askrjs/server/router (also re-exported from the package root) returns a Router with no routes registered yet. Router carries the same verb methods as RouteBuilder — 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 straight into createServerApp(router), or nest it inside a ServerAppOptions object alongside other cross-cutting config.

Define routes

Each HTTP verb has its own method — router.get(path, handler), router.post(...), and so on through trace and connect — plus a generic router.route(method, path, handler, options) that accepts a single verb or an array of them. defineRoutes(definition) is the alternative entry point: instead of mutating a Router, you get a RouteBuilder inside a callback and collect the results into a plain ApiRoute[], which is useful when you want route definitions as data before deciding how to mount them. Both paths accept the same ApiRouteOptions — auth and per-route middleware — as the handler's third argument.

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. ServerAppOptions also accepts a standalone routes array alongside router, so ApiRoute[] produced by defineRoutes() can be merged in without going through a Router instance at all.