Your route table is a plain object. Keys are path patterns, values are handler functions. See Request for what a handler receives.

const routes = { "/hello": () => res("Hello World!"), "/users/:id": ({ matches }) => res({ id: matches.params.id }), "/posts/:id/comments/:commentId": getPostComment };

Path parameters

Patterns use path-to-regexp syntax. Named parameters arrive on matches.params, already decoded.

"/users/:id" -> /users/42 matches.params.id === "42" "/posts/:id/comments/:commentId" -> /posts/5/comments/10 "/files/:path*" -> /files/a/b/c matches.params.path === "a/b/c"

HTTP methods

A route matches every method. Branch inside the handler on request.method, and answer anything you do not support with res_405.

1const routes = {
2 "/users/:id": async ({ request, matches, body, state }) => {
3 const id = matches.params.id;
4
5 if (request.method === "GET") return res(await state("Users", id).get("profile"));
6 if (request.method === "POST") {
7 await state("Users", id).put({ profile: body });
8 return res_201();
9 }
10
11 return res_405();
12 }
13};

For larger APIs, define handlers as named functions in their own modules and reference them directly by name — there is no separate registration step.

Matching order

The first pattern that matches wins, so declare specific paths before catch-alls. If nothing matches, Apiker answers 404. If a handler returns nothing, the response is 204.

Enabling authRoutes or adminPanel prepends those routes to your table, so they are matched before your own.