# @wrnexus/router > File-based router that maps an `app/` directory onto route tables and matches request paths against them. Part of the **WrNexus** framework — an SSR-first, Bun-native full-stack web framework. ## Overview `@wrnexus/router` scans an application's `app/` directory once at startup and builds route tables for pages, API endpoints, realtime channels, middleware, server-rendered `.wrn` components, layouts, and validation schemas. It also compiles URL patterns (`/users/[id]`) into RegExps and matches request paths against them. Request input is never turned into a file path, which makes the router immune to path traversal. This is a server-side package used by the WrNexus runtime to resolve incoming requests, plus a codegen helper for compile-time typed links. ## Installation ```bash bun add @wrnexus/router ``` > Private package — the machine must be authenticated to the `wrnexus` npm org > (a read token in `~/.npmrc`). Requires **Bun** (Node is not supported). ## Directory conventions The router maps files under `appDir` onto routes: ``` app/pages/index.tsx -> GET / app/pages/about.tsx -> GET /about app/pages/users/[id].tsx -> GET /users/:id app/api/hello.ts -> /api/hello app/realtime/chat.ts -> /realtime/chat app/pages/*.wrn (api) -> embedded /api/* routes app/pages/*.wrn (rt) -> embedded /realtime/* routes app/middleware/*.ts -> global middleware (alphabetical) app/components/*.wrn -> server-rendered components (by basename) app/layouts/*.wrn -> named page layouts app/schemas/*.ts -> validation schemas ``` Allowed route extensions are `.ts`, `.tsx`, and `.wrn`. Dotfiles and underscore-prefixed files are ignored. A trailing `index` segment is dropped from the route. `.wrn` pages may embed `api` and `realtime` blocks, which the router extracts and mounts under `/api/*` and `/realtime/*`. ## API ### `buildRouter(appDir, opts?): Router` Scan an app directory and build all route tables. ```ts function buildRouter(appDir: string, opts?: RouterOptions): Router; interface RouterOptions { /** Extra dirs scanned for `.wrn` components (e.g. `@wrnexus/ui`), before * `app/components`, so an app component of the same name wins. */ componentDirs?: string[]; } ``` The returned `Router` exposes the built tables plus per-kind matchers: ```ts interface Router { pages: Route[]; api: Route[]; realtime: Route[]; /** Absolute paths of middleware modules, in execution order (alphabetical). */ middlewareFiles: string[]; /** Server-rendered `.wrn` components, mounted via `data-component`. */ components: ComponentRef[]; /** Named page layouts (`app/layouts/.wrn`); a page picks one via `layout`. */ layouts: ComponentRef[]; /** Validation schemas (`app/schemas/.ts`) shared by API + forms. */ schemas: ComponentRef[]; matchPage(pathname: string): RouteMatch | null; matchApi(pathname: string): RouteMatch | null; matchRealtime(pathname: string): RouteMatch | null; } interface ComponentRef { /** Validated component name (matches a `data-component` attribute). */ name: string; /** Absolute path to the component's `.wrn` module. */ file: string; } ``` Component, layout, and schema names are validated with `isSafeIslandName` from `@wrnexus/core`; unsafe names are skipped with a warning. Realtime channel names are validated the same way. ### Route matching | Export | Signature | Description | | --------------------- | ----------------------------------------------------------- | ------------------------------------------------------------------------------------------------------------------- | | `compileRoutePattern` | `(raw: string) => Pick` | Compile a `/users/[id]` pattern into a RegExp (with optional trailing slash) plus ordered param names. | | `matchRoute` | `(routes: Route[], pathname: string) => RouteMatch \| null` | Return the first route whose regex matches; captured params are `decodeURIComponent`-decoded. | | `sortRoutes` | `(routes: Route[]) => Route[]` | Order routes so static routes win over dynamic ones (fewer params first), then longer/more specific patterns first. | ```ts interface Route { raw: string; // e.g. "/users/[id]" file: string; // absolute path to the handling module regex: RegExp; // compiled matcher paramNames: string[]; // ordered dynamic param names } interface RouteMatch { route: Route; params: Record; } ``` ### Typed-routes codegen ```ts function generateRoutesFile(pages: Route[]): string; ``` Emits the source for `app/routes.gen.ts`: a `Routes` map (each page path → its `[param]` types), a `RoutePath` union, and an `href()` builder that fills params and rejects unknown paths at compile time. Entries are de-duplicated and sorted by path. ### Re-exports `Middleware` (the type from `@wrnexus/core`) is re-exported for callers that load middleware modules themselves. ## Usage ```ts import { buildRouter } from "@wrnexus/router"; const router = buildRouter("./app", { componentDirs: ["./node_modules/@wrnexus/ui/components"], }); // Resolve an incoming request. const match = router.matchPage("/users/42"); if (match) { console.log(match.route.file); // absolute path to the page module console.log(match.params); // { id: "42" } } const api = router.matchApi("/api/hello"); const rt = router.matchRealtime("/realtime/chat"); ``` Generating the typed-routes file (as `wrnexus dev` does): ```ts import { generateRoutesFile } from "@wrnexus/router"; import { writeFileSync } from "node:fs"; const router = buildRouter("./app"); writeFileSync("./app/routes.gen.ts", generateRoutesFile(router.pages)); ``` ```ts // Then, in app code, links are checked at compile time: import { href } from "./routes.gen.ts"; href("/users/[id]", { id: "42" }); // "/users/42" href("/about"); // "/about" href("/nope"); // type error: unknown path ``` Lower-level pattern matching, if you need it directly: ```ts import { compileRoutePattern, matchRoute, sortRoutes, type Route } from "@wrnexus/router"; const { regex, paramNames } = compileRoutePattern("/posts/[slug]"); const routes = sortRoutes([{ raw: "/posts/[slug]", file: "…", regex, paramNames }]); const m = matchRoute(routes, "/posts/hello"); // { route, params: { slug: "hello" } } ``` ## Requirements / Notes - Scanning uses `node:fs` (`existsSync`, `readdirSync`, `statSync`) and `node:path` — runs under Bun. - Depends on [`@wrnexus/compiler`](../compiler) to `parse` `.wrn` pages and extract embedded `api` / `realtime` blocks. - Depends on [`@wrnexus/core`](../core) for `isSafeIslandName` (name validation) and the `Middleware` type. - Missing route directories are tolerated — a route kind you don't use simply yields an empty table.