Skip to content

A tiny (1.3 kB) middleware style router for your Node.js HTTP server.

License

Notifications You must be signed in to change notification settings

Kikobeats/router-http

Folders and files

NameName
Last commit message
Last commit date

Latest commit

 

History

76 Commits
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 

Repository files navigation

router-http

Last version Coverage Status NPM Status

A middleware-style router similar to express router, with key advantages:

  • Predictable performance – Backed by find-my-way, a trie-based router with constant O(1) lookup time.
  • Battle-tested – Well maintained with comprehensive test coverage.
  • Lightweight – Only 1.3 kB (minifized + gzipped)

Why not Express router?

Express uses regex-based route matching that degrades linearly as routes increase:

Routes express@router router-http
5 ~10.7M ops/sec ~13.7M ops/sec
10 ~6.5M ops/sec ~13.7M ops/sec
50 ~1.5M ops/sec ~11.5M ops/sec
1000 ~41K ops/sec ~10.6M ops/sec

In contrast, router-http is backed by a trie-based implementation that maintains nearly constant performance regardless of the number of routes.

Installation

npm install router-http

Getting Started

First, define a handler for errors and unmatched routes:

const createRouter = require('router-http')

const finalHandler = (error, req, res) => {
  if (error) {
    res.statusCode = 500
    res.end(error.message)
  } else {
    res.statusCode = 404
    res.end('Not Found')
  }
}

const router = createRouter(finalHandler)

You can also pass find-my-way options as a second argument:

const router = createRouter(finalHandler, {
  caseSensitive: false,
  ignoreTrailingSlash: true
})

Declaring routes

Use HTTP verb methods to define your routes:

router
  .get('/', (req, res) => res.end('Hello World'))
  .post('/users', (req, res) => res.end('User created'))
  .put('/users/:id', (req, res) => res.end('User updated'))
  .delete('/users/:id', (req, res) => res.end('User deleted'))

Use .all() to match any HTTP method:

router.all('/ping', (req, res) => res.end('pong'))

The dynamic segments will be captured using the :param syntax, with parameters accessible via req.params:

router.get('/users/:id', (req, res) => {
  res.end(`User ID: ${req.params.id}`)
})

router.get('/posts/:year/:month', (req, res) => {
  const { year, month } = req.params
  res.end(`Posts from ${month}/${year}`)
})

See Request object for details on how to access route parameters and other useful properties added to req.

Declaring middlewares

A middleware is declared using .use(). it will be added globally before running any route:

// Global middleware (runs on every request)
router
  .use((req, res, next) => {
    req.timestamp = Date.now()
    next()
})

You can also declare middleware specific to routes:

const auth = (req, res, next) => { /* verify token */ next() }
const log = (req, res, next) => { /* log request */ next() }

const protected = [auth, log]

router
  .get('/admin', protected, (req, res) => res.end('Admin panel'))
  .get('/settings', protected, (req, res) => res.end('Settings'))

If you want to add a middleware conditionally, just return a falsy value:

router
  .use(process.env.NODE_ENV === 'production' && rateLimiter())

Starting the server

The router is a standard request handler. Pass it to http.createServer:

const http = require('http')

console.log(router.prettyPrint())
// └── / (GET)
//     ├── favicon.ico (GET)
//     └── user/
//         └── :id (GET)

http.createServer(router).listen(3000)

Advanced

Request object

The router adds these properties to req:

Property Description
req.path URL pathname
req.params Route parameters object
req.query Raw query string (after ?)
req.search Raw search string (including ?)

req.query and req.search are only set if not already present.

Print routes

You can visualize your router's routes in a readable tree format using the router.prettyPrint() method. This is especially helpful for debugging or understanding your route structure at a glance.

For example:

const http = require('http')

console.log(router.prettyPrint())
// └── / (GET)
//     ├── favicon.ico (GET)
//     └── user/
//         └── :id (GET)

http.createServer(router).listen(3000)

The printed output shows the nested structure of your routes along with their registered HTTP methods. This works for both flat and deeply nested routers, including those mounted via .use().

See more in find-my-way prettyPrint documentation.

Nested routers

You can use a router as a middleware for another router. This is useful for prefixing routes or for modularizing your application:

const createRouter = require('router-http')
const http = require('http')

const final = (err, req, res) => {
  res.statusCode = err ? 500 : 404
  res.end(err ? err.message : 'Not Found')
}

// 1. Create a sub-router for API v1
const v1 = createRouter(final)
v1.get('/info', (req, res) => res.end('v1 info'))

// 2. Create another sub-router for API v2
const v2 = createRouter(final)
v2.get('/info', (req, res) => res.end('v2 info'))

// 3. Create the main router and mount sub-routers
const router = createRouter(final)

router
  .use('/v1', v1)
  .use('/v2', v2)
  .get('/', (req, res) => res.end('Welcome to the main router'))

http.createServer(router).listen(3000)

When a sub-router is used as a middleware, it will only handle requests that match its prefix. If no route matches inside the sub-router, it will automatically call next() to pass control back to the parent router.

Skipping to parent router

Use next('router') to exit the current router and pass control back to the parent:

const beta = createRouter(finalHandler)

beta.use((req, res, next) => {
  if (!req.isBetaTester) return next('router')
  next()
})

beta.get('/feature', (req, res) => res.end('Beta feature'))

router.use('/v1', beta)
router.get('/v1/feature', (req, res) => res.end('Stable feature'))

Benchmark

With all the improvements, router-http is approximately 30% faster than the express router:

express@5.2.1

Running 30s test @ http://localhost:3000/user/123
  8 threads and 100 connections
  Thread Stats   Avg      Stdev     Max   +/- Stdev
    Latency     1.23ms    1.40ms  96.27ms   99.61%
    Req/Sec    10.15k   615.89    11.07k    86.24%
  2430687 requests in 30.10s, 356.98MB read
Requests/sec:  80752.48
Transfer/sec:     11.86MB

router-http

Running 30s test @ http://localhost:3000/user/123
  8 threads and 100 connections
  Thread Stats   Avg      Stdev     Max   +/- Stdev
    Latency     0.97ms    1.27ms  84.82ms   99.77%
    Req/Sec    12.91k     1.07k   14.67k    71.51%
  3092927 requests in 30.10s, 386.40MB read
Requests/sec: 102751.65
Transfer/sec:     12.84MB

See benchmark for details.

Related

License

router-http © Kiko Beats, released under the MIT License.

Credits to Luke Edwards for Polka which inspired this project.

About

A tiny (1.3 kB) middleware style router for your Node.js HTTP server.

Topics

Resources

License

Stars

Watchers

Forks

Packages

 
 
 

Contributors