routekit
v0.6.0
Published
Routing library
Downloads
99
Readme
routekit
Route matching library.
- Efficient O(n) path matching
- Compact flat array serialization format
- Zero NPM dependencies
How it works
Generating routes Module
- Build — Route declarations are converted into a trie.
- Optimize — The trie is flattened into a compact and efficient array format.
- Serialize — The trie is serialized into a js or ts module.
Resolving Routes
- Import — Generated
routesmodule is imported. - Resolve — A depth-first search walks the trie and finds a match.
Install
npm install routekitUsage
1. Define Routes
// gen-routes.ts
import { r, emitRoutes } from 'routekit/emit';
await emitRoutes({
routes: [
r('/', '"home"'),
r('/about', '"about"'),
r('/users/:id', '"user"'),
r('/users/:id/posts', '"user-posts"'),
r('/files/*path', '"file"'),
],
path: './src/routes.ts',
});It will generate src/routes.ts:
/* File is autogenerated by "routekit" */
import { resolve } from 'routekit';
const ROUTES = {
f: [
/* Flags */
],
p: [
/* Path Segments */
],
s: [
/* State */
],
};
export const routeMatch = (path: string) => resolve(ROUTES, path);2. Match paths
import { routeMatch } from './src/routes.ts';
{
const match = routeMatch('/users/42');
if (match) {
console.log(match.state); // "user"
console.log(match.vars); // ["42"]
}
}
{
const match = routeMatch('/files/docs/readme.md');
console.log(match?.vars); // ["docs/readme.md"]
}Route syntax
- Static segments:
/about,/users/list - Variable segments:
:name— captures the segment value - Catch-all segments:
*name— captures all remaining path segments
r('/', 'home');
r('/blog/:slug', 'post');
r('/blog/:slug/comments/:cid', 'comment');
r('/files/*path', 'file');API
routekit/emit
r(path: string, state: string | number)
Creates a route definition.
r('/products/:id', '"product-detail"');
r('/users/:id', 42);emitRoutes(options)
Builds the route trie and writes the generated file.
Options:
routes— Route definitions.path— Output file path. Optional; if omitted, returns theFlatTree.prelude— Custom code inserted after the defaultimport { resolve } from "routekit"line.type— State type.
await emitRoutes({
routes: [r('/users/:id', '{ group: "users", route: "view" }')],
path: './src/routes.ts',
prelude: `import type { RouteState } from './types.js';`,
type: 'RouteState',
});routekit
resolve(map, path)
Resolves a URL path against a RouteMap. Returns { state, vars } or null.
import { resolve } from 'routekit';
const result = resolve(ROUTES, '/users/42');
// { state: "user", vars: ["42"] }Best Practices
Handler Functions
// src/routes/handlers.ts
export type RouteHandler = (vars: string[]) => void;
export const home: RouteHandler = () => {};
export const dashboard: RouteHandler = () => {};
export const settings: RouteHandler = () => {};// routes.ts
await emitRoutes({
routes: [r('/', 'h.home'), r('/dashboard', 'h.dashboard'), r('/settings', 'h.settings')],
path: './src/routes/match.ts',
prelude: `import * as h from './handlers.js';`,
type: 'h.RouteHandler',
});// Usage
const match = routeMatch('/dashboard');
if (match) {
match.state(match.vars);
}Caveats
- No trailing-slash normalization —
/usersand/users/are distinct routes. If a request comes in with a trailing slash, it must match a route defined with a trailing slash. - Catch-all matches empty string at trailing slash —
/files/*pathmatches/files/withvars: [""]. - Duplicate routes overwrite — defining two routes with the same path overwrites the first silently.
- Variable names not captured —
varscontains only captured values, in order of appearance. For/users/:id, the result hasvars: ["42"]. - Catch-all must be last — catch-all segments (
*name) must be the last segment in a route pattern. - Empty variable segments don't match —
/users/:iddoes not match/users/. - Static routes take precedence — when both
/users/listand/users/:idexist, the static route wins for/users/list.
License
MIT OR Apache-2.0
