npm package discovery and stats viewer.

Discover Tips

  • General search

    [free text search, go nuts!]

  • Package details

    pkg:[package-name]

  • User packages

    @[username]

Sponsor

Optimize Toolset

I’ve always been into building performant and accessible sites, but lately I’ve been taking it extremely seriously. So much so that I’ve been building a tool to help me optimize and monitor the sites that I build to make sure that I’m making an attempt to offer the best experience to those who visit them. If you’re into performant, accessible and SEO friendly sites, you might like it too! You can check it out at Optimize Toolset.

About

Hi, 👋, I’m Ryan Hefner  and I built this site for me, and you! The goal of this site was to provide an easy way for me to check the stats on my npm packages, both for prioritizing issues and updates, and to give me a little kick in the pants to keep up on stuff.

As I was building it, I realized that I was actually using the tool to build the tool, and figured I might as well put this out there and hopefully others will find it to be a fast and useful way to search and browse npm packages as I have.

If you’re interested in other things I’m working on, follow me on Twitter or check out the open source projects I’ve been publishing on GitHub.

I am also working on a Twitter bot for this site to tweet the most popular, newest, random packages from npm. Please follow that account now and it will start sending out packages soon–ish.

Open Software & Tools

This site wouldn’t be possible without the immense generosity and tireless efforts from the people who make contributions to the world and share their work via open source initiatives. Thank you 🙏

© 2025 – Pkg Stats / Ryan Hefner

@neabyte/fast-router

v0.1.0

Published

A fast, versatile router with radix tree structure for JavaScript

Readme

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.

Table of Contents

Quick Start

Installation

# npm package
npm install @neabyte/fast-router

# Deno module
deno add jsr:@neabyte/fast-router

Basic Usage

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'

Examples

Data Type Flexibility

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

Named Parameters

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'

Wildcard Parameters

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'

Catch-All Wildcard

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'

Regex Parameter Validation

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'

HTTP Method Support

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'

Static vs Parameter Routes

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'

Trailing Slash Normalization

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'

Complex Nested Routes

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'

Disable Parameter Extraction

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

Route Removal

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

API Reference

add

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

find

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
}

remove

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 true if successfully removed, false if not found.

Testing

Run the test suite:

deno task test

Format and lint:

deno task check

Run the benchmarks:

deno task bench

License

This project is licensed under the MIT license. See the LICENSE file for more info.