Fast Router Module type: CJS+ESM npm version JSR Node.js CI
A fast, versatile router with radix tree structure for JavaScript
Note
This project is inspired by rou3 and uses similar routing algorithm and tree structure. It provides a class-based API as an alternative to rou3's functional approach, suitable for object-oriented use cases.
# npm package npm install @neabyte/fast-router # Deno module deno add jsr:@neabyte/fast-router
import { FastRouter } from '@neabyte/fast-router' // Create a new router instance const router = new FastRouter() // Add routes with data router.add('GET', '/users/:id', { handler: 'getUser' }) router.add('GET', '/users', { handler: 'listUsers' }) router.add('GET', '/posts/*', { handler: 'posts' }) // Find matching route const match = router.find('GET', '/users/123') console.log(match?.data?.handler) // 'getUser' console.log(match?.params?.id) // '123'
Fast Router supports any data type via TypeScript generics. You're not limited to handler objects!
// String data const router1 = new FastRouter<string>() router1.add('GET', '/users/:id', 'getUser') const match1 = router1.find('GET', '/users/123') console.log(match1?.data) // 'getUser' // Number data const router2 = new FastRouter<number>() router2.add('GET', '/config/:key', 42) const match2 = router2.find('GET', '/config/value') console.log(match2?.data) // 42 // Array data const router3 = new FastRouter<string[]>() router3.add('GET', '/posts', ['list', 'posts']) const match3 = router3.find('GET', '/posts') console.log(match3?.data) // ['list', 'posts'] // Function data const handler = (req: Request) => new Response('ok') const router4 = new FastRouter<typeof handler>() router4.add('GET', '/api/data', handler) const match4 = router4.find('GET', '/api/data') console.log(typeof match4?.data) // 'function' // Complex object interface RouteConfig { handler: string middleware: string[] timeout: number } const router5 = new FastRouter<RouteConfig>() router5.add('GET', '/secure', { handler: 'protected', middleware: ['auth', 'rate-limit'], timeout: 5000 }) const match5 = router5.find('GET', '/secure') console.log(match5?.data) // Full object // Class instance class MyHandler { name: string constructor(name: string) { this.name = name } } const router6 = new FastRouter<MyHandler>() router6.add('GET', '/custom', new MyHandler('test')) const match6 = router6.find('GET', '/custom') console.log(match6?.data?.name) // 'test' // No data (undefined) const router7 = new FastRouter<undefined>() router7.add('GET', '/ping') const match7 = router7.find('GET', '/ping') console.log(match7?.data) // null
const router = new FastRouter() // Route with named parameter router.add('GET', '/users/:id', { handler: 'getUser' }) const userMatch = router.find('GET', '/users/123') console.log(userMatch?.data?.handler) // 'getUser' console.log(userMatch?.params?.id) // '123' // Route with named parameters router.add('GET', '/users/:id/posts/:postId', { handler: 'getPost' }) const postMatch = router.find('GET', '/users/123/posts/456') console.log(postMatch?.data?.handler) // 'getPost' console.log(postMatch?.params?.id) // '123' console.log(postMatch?.params?.postId) // '456'
const router = new FastRouter() // Single wildcard (matches one segment) router.add('GET', '/posts/*', { handler: 'posts' }) const match = router.find('GET', '/posts/javascript') console.log(match?.data?.handler) // 'posts' console.log(match?.params?._0) // 'javascript'
const router = new FastRouter() // Catch-all wildcard (matches multiple segments) router.add('GET', '/files/**', { handler: 'files' }) const match = router.find('GET', '/files/docs/user/guide.pdf') console.log(match?.data?.handler) // 'files' // Named catch-all router.add('GET', '/files/**:name', { handler: 'fileName' }) const namedMatch = router.find('GET', '/files/docs/user/guide.pdf') console.log(namedMatch?.params?.name) // 'docs/user/guide.pdf'
const router = new FastRouter() // Validate numeric ID router.add('GET', '/user/:id(\\d+)', { handler: 'numericUser' }) const validMatch = router.find('GET', '/user/123') console.log(validMatch?.data?.handler) // 'numericUser' const invalidMatch = router.find('GET', '/user/abc') console.log(invalidMatch) // undefined (regex validation failed) // Validate phone format router.add('GET', '/phone/:number(\\d{3}-\\d{3}-\\d{4})', { handler: 'phoneFormat' }) const phoneMatch = router.find('GET', '/phone/123-456-7890') console.log(phoneMatch?.params?.number) // '123-456-7890'
const router = new FastRouter() // Method-specific route (GET) router.add('GET', '/users/:id', { handler: 'getUser' }) const getMatch = router.find('GET', '/users/123') console.log(getMatch?.data?.handler) // 'getUser' // Method-specific route (PUT) router.add('PUT', '/users/:id', { handler: 'updateUser' }) const putMatch = router.find('PUT', '/users/123') console.log(putMatch?.data?.handler) // 'updateUser' // Method-agnostic route router.add('', '/api/data', { handler: 'anyMethod' }) const anyMatch = router.find('POST', '/api/data') console.log(anyMatch?.data?.handler) // 'anyMethod'
const router = new FastRouter() // Static route router.add('GET', '/users/admin', { handler: 'adminRoute' }) const adminMatch = router.find('GET', '/users/admin') console.log(adminMatch?.data?.handler) // 'adminRoute' // Parameter route router.add('GET', '/users/:id', { handler: 'userRoute' }) const userMatch = router.find('GET', '/users/123') console.log(userMatch?.data?.handler) // 'userRoute'
const router = new FastRouter() // Add route with trailing slash router.add('GET', '/api/data', { handler: 'data' }) // Both paths work const withSlash = router.find('GET', '/api/data/') const withoutSlash = router.find('GET', '/api/data') console.log(withSlash?.data?.handler) // 'data' console.log(withoutSlash?.data?.handler) // 'data'
const router = new FastRouter() // Multiple parameters and wildcards router.add('GET', '/api/:version/posts/:id/comments', { handler: 'comments' }) const match = router.find('GET', '/api/v1/posts/123/comments') console.log(match?.params?.version) // 'v1' console.log(match?.params?.id) // '123' // Mixed wildcards router.add('GET', '/data/:id/*/**:rest', { handler: 'mixed' }) const wildMatch = router.find('GET', '/data/123/x/y/z') console.log(wildMatch?.params?.id) // '123' console.log(wildMatch?.params?._0) // 'x' console.log(wildMatch?.params?.rest) // 'y/z'
const router = new FastRouter() // Add route with parameter router.add('GET', '/users/:id', { handler: 'getUser' }) // Extract parameters (default) const withParams = router.find('GET', '/users/123') console.log(withParams?.params?.id) // '123' // Disable parameter extraction const withoutParams = router.find('GET', '/users/123', { params: false }) console.log(withoutParams?.params) // undefined
const router = new FastRouter() // Add routes with different methods router.add('GET', '/users/:id', { handler: 'getUser' }) router.add('PUT', '/users/:id', { handler: 'updateUser' }) router.add('GET', '/posts/*', { handler: 'posts' }) // Remove specific method route const removed = router.remove('GET', '/users/:id') console.log(removed) // true // Verify removal const getMatch = router.find('GET', '/users/123') console.log(getMatch) // undefined (route was removed) // Other method still works const putMatch = router.find('PUT', '/users/123') console.log(putMatch?.data?.handler) // 'updateUser' // Remove wildcard route router.remove('GET', '/posts/*') const postMatch = router.find('GET', '/posts/javascript') console.log(postMatch) // undefined // Remove non-existent route returns false const notFound = router.remove('GET', '/nonexistent') console.log(notFound) // false
router.add(method, path, data?)
method<string>: HTTP method (GET, POST, PUT, DELETE, etc.). Use empty string.path<string>: The route path pattern (supports:param,*,**wildcards).data<T>: (Optional) Data to associate with the route.- Returns:
void - Description: Add a route to the router with optional data and HTTP method.
Route Patterns:
:param- Named parameter (matches a single segment):param(regex)- Named parameter with regex validation*- Wildcard (matches a single segment)**- Catch-all wildcard (matches remaining segments)**:name- Named catch-all wildcard
router.find(method, path, opts?)
method<string>: HTTP method to match. Use empty string.path<string>: The path to search for.opts<object>: (Optional) Search options.params<boolean>: (Optional) Whether to extract parameters. Defaults totrue.
- Returns:
<RouterMatchedRoute<T> | undefined> - Description: Find matching route for the given path and HTTP method. Returns undefined if no match is found.
Return Type:
{ data: T, // Data associated with the matched route params: Record<string, string> | undefined // Extracted route parameters }
router.remove(method, path)
method<string>: HTTP method to remove. Use empty string.path<string>: The route path pattern to remove (supports:param,*,**wildcards).- Returns:
<boolean> - Description: Remove a route from the router. Returns
trueif successfully removed,falseif not found.
Run the test suite:
deno task testFormat and lint:
deno task check
Run the benchmarks:
deno task bench
This project is licensed under the MIT license. See the LICENSE file for more info.