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-routerimport { 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) // nullconst 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) // undefinedconst 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) // falserouter.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 to- true.
 
- 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 checkRun the benchmarks:
deno task benchThis project is licensed under the MIT license. See the LICENSE file for more info.