# Router Routing in Elements maps each HTTP url to a function handler that responds to requests at that url. Routes are declared explicitly with `app.route()` in the project's root `index.ts`. The `app/pages/` folder is a convention for storing page templates and route logic, and folder names do not affect routing at all. The router works equally well for serving html pages and serving api endpoints. URL patterns are full `URLPattern` expressions, the web standard for url pattern matching. Patterns support `:name` segments, `*` wildcards, optional groups, and inline regex like `:id(\d+)`. The second argument to `app.route()` is either a handler function or a child router. Routes are the entry point for html pages. A typical route handler will gather the data the page needs and return a new html template whose parameters are provided by the route function. Elements server-renders the html, sends the page along with any live tables, channels, and other reactive values it carries over the wire, and attaches it in the browser. Route handlers have the same data features as `@rpc` functions. The `session` global is available with the current user. Return an html template and Elements server-renders it. Return an object or array and Elements serializes it through the extended json serializer, preserving class identity, handling object cycles, and serializing live tables. Return a string and it goes out as `text/plain`. Write to `res` directly and return nothing. `app.use()` registers a handler that runs before any route matches. Child routers can define their own `use()` handlers. This is where cross-cutting concerns like logging, custom headers, and feature flags go. Body parsing, sessions, asset serving, and form handling are built in. `app.route()` and `app.use()` register against the app's default router. Larger apps create child routers with `new Router()` and mount them at a path prefix like `/admin/*`. Routers nest as deep as needed. Routing runs on the server by design. The browser attaches to whatever the route renders. ## At a Glance ```ts // index.ts import { App } from "@elements/app"; import config from "#config"; import home from "#app/pages/home"; import roomsShow from "#app/pages/rooms-show"; import upload from "#app/pages/upload"; import api from "#app/api"; const app = new App(); app.use((req, res, next) => { console.log(`${req.method} ${req.url}`); next(); }); app.route("/", home); app.route("/rooms/:id", roomsShow); app.route({ method: "post", path: "/upload", handler: upload }); app.route("/api/*", api); app.error((req, res, err) => { res.status(err.statusCode ?? 500).send(`error: ${err.message}`); }); app.start(config); ``` ## Handlers A handler is a function or an object with a `handle()` method. ```ts type Handler = HandlerFunction | HandlerObject; type HandlerFunction = (req: Request, res: Response) => HandlerResult | Promise; type HandlerObject = { handle(req: Request, res: Response): HandlerResult | Promise; mount?: boolean; }; ``` A page's `index.ts` exports a route handler as the default export: ```ts // app/pages/home/index.ts import html from "./template"; export default function route(req, res) { let users = sql(`select * from users`).all(); return new html({ users }); } ``` ## Return Values Whatever a handler returns becomes the response. - A `Sendable` (for example, an html template instance) is sent with ETag and gzip. - A `string` is sent as `text/plain`. - An object or array is serialized as Elements json and sent. - `void` or `undefined` means the handler used `res` directly. ```ts app.route("/api/rooms", () => sql(`select id, name from rooms order by createdAt desc`).all()); app.route("/health", (req, res) => "ok"); app.route("/redirect", () => { redirect("/"); }); ``` ## URL Patterns Routes are `URLPattern` expressions. Dynamic segments use `:name`, wildcards use `*`, optional groups use `?`, and inline regex goes inside parentheses. ```ts app.route("/", home); app.route("/rooms/:id", roomsShow); app.route("/posts/:id/comments/:commentId", showComment); app.route("/files/*", fileServer); app.route("/users/:id(\\d+)", showUser); app.route("/archive/:year/:month?", archive); ``` Match parameters land in `req.params`: ```ts function route(req, res) { let id = req.params.id; let cid = req.params.commentId; } ``` Folder names under `app/pages/` do not affect routing. The route url is whatever you pass to `app.route()`. ## Method Matching A route matches any method by default. ```ts app.route("/api/users", listUsers); // any method app.route({ method: "post", path: "/api/users", handler: createUser }); // POST only app.route({ method: ["get", "post"], path: "/api/data", handler: data }); // GET or POST ``` ## Domain Matching Restrict a route to a specific hostname. ```ts app.route({ domain: "api.example.com", path: "/v1/*", handler: apiRouter }); ``` ## `use()` Handlers `app.use()` registers a handler that runs before any route on the router matches. Call `next()` to continue, send a response to stop the chain, or throw to trigger the error handler. Handlers run in registration order. ```ts app.use((req, res, next) => { req.startTime = Date.now(); next(); }); ``` Each router has its own `use()`. A handler registered on a child router only runs for that router's routes. ```ts import { Router } from "@elements/app"; const api = new Router(); api.use((req, res, next) => { res.setHeader("Cache-Control", "no-store"); next(); }); api.route("/users", listUsers); app.route("/api/*", api); ``` Body parsing, session loading, asset serving, and form handling are built in. Reach for `use()` when you need a cross-cutting concern Elements doesn't already cover: request logging, custom headers, feature flags. ## Sub-Routers Nested routers mount at a path prefix. ```ts import { Router } from "@elements/app"; const api = new Router(); api.route("/users", listUsers); api.route("/users/:id", getUser); app.route("/api/*", api); ``` Inside `api`, the URL has the `/api` prefix stripped (`/users`, not `/api/users`). The `Router` class has `mount: true` by default. ## Error Handler ```ts app.error((req, res, err) => { switch (err.statusCode) { case 404: return notFound(req, res, err); default: return unhandled(req, res, err); } }); ``` The error handler runs when a route handler throws or when no route matches. Throwable error types from `@elements/app`: - `AuthError` (401): unauthenticated. - `ForbiddenError` (403): authenticated but not permitted. - `NotFoundError` (404): resource missing. - `ValidationError` (422): invalid input. - `NotAcceptableError` (406): request body unparseable. - `ScopeMismatchError` (400): LiveTable scope mismatch. Throw them directly when something is wrong: ```ts import { NotFoundError } from "@elements/app"; let room = sql(`select * from rooms where id = ${req.params.id}`).first(); if (!room) { throw new NotFoundError(`room ${req.params.id} not found`); } ``` Internal or unsafe errors are wrapped in `ServerError` before reaching the client. ## First-Match-Wins Routes match in registration order. The first route that matches wins. Order specific routes before wildcards: ```ts // correct app.route("/api/users", listUsers); app.route("/api/*", api); // wrong: api/* catches /api/users first app.route("/api/*", api); app.route("/api/users", listUsers); ``` ## Redirecting `redirect(url)` sends the visitor somewhere else. It is the same call templates use, so a guard reads identically in a route, in a `use()` handler, and in a template event handler. ```ts import { redirect, session } from "@elements/app"; export default function route(req, res) { if (!session.isLoggedIn()) { redirect("/signin"); return; } return new dashboard(); } ``` It writes a 302 and ends the response. It is not control flow: the handler keeps running, and a value returned afterward is discarded rather than written over the redirect, so both the early-return shape above and a plain fall-through work. Return early when you want the rest of the handler skipped. For a status other than 302, use `res.redirect(url, 301)`. ## The Request and Response `req` and `res` are enhanced Node `IncomingMessage` and response objects. ```ts req.method req.url // current routing-context URL (sub-routers may mount-strip a prefix) req.originalUrl // original URL, never modified by routers req.parsedUrl // URL object for the current routing context req.params // merged: query < body < path. path wins. req.path // path-only params from the URLPattern match req.query // query-string only (string | string[] values) req.body // parsed body (json, form-data with File instances, urlencoded) req.bodyBuffer // raw body buffer res.status(404) res.send(value) // any sendable: string, object, html instance res.redirect("/", 301) // status other than 302; otherwise use redirect(url) res.end() ``` Access the current session via the global `session` namespace: ```ts import { session, AuthError } from "@elements/app"; app.route("/me", (req, res) => { if (!session.isLoggedIn()) { throw new AuthError(); } return sql(`select * from users where id = ${session.getOrThrow("userId")}`).firstOrThrow("not found"); }); ``` ## Related - `start`: the `App` instance, `app.start()`, application structure. - `rpc`: the `@rpc` boundary, authorization, error catalog. - `session`: reactive session, login/logout, sliding window. - `html`: html templates and the route-to-page flow.