1.0.1 • Published 12 months ago

instant-router v1.0.1

Weekly downloads
-
License
MIT
Repository
github
Last release
12 months ago

Instant Router

Ultra fast router to match incoming HTTP requests and generate urls.

  • Uses a radix tree datastructure.
  • Highly optimized to match static routes, check the benchmarks.
  • UrlGenerator for relative/absolute urls.
  • No middlewares.
  • Framework independent.

Summary

Install

npm install instant-router
yarn add instant-router

Usage

Matching

import Router, { HttpContext, RouteDefinition, MethodNotAllowedError, RequestContext, ResourceNotFound } from 'instant-router';
import http, { IncomingMessage, ServerResponse } from 'node:http';

const routes: RouteDefinition[] = [
  {
    path: '/static',
    controller: ({ res }: HttpContext) => res.end('Hello, World!')
  },
  {
    path: '/users/:name',
    requirements: { name: '[a-zA-Z]+' },
    controller: ({ params, res }: HttpContext) => res.end(`Hello ${params.name}!`)
  }
]

const router = new Router()
routes.forEach(route => router.addRoute(route))

http.createServer((request: IncomingMessage, res: ServerResponse) => {
  try {
    const req = RequestContext.fromIncomingMessage(request)
    const { controller, params } = router.match(req);

    controller({ req, res, params })
  } catch (error) {
    console.error(error)
    if (error instanceof ResourceNotFound) {
      res.writeHead(404)
      res.end('404 - Resource not found')
    } else if (error instanceof MethodNotAllowedError) {
      res.writeHead(405)
      res.end(`405 - Method ${request.method} not allowed`)
    } else {
      res.writeHead(500)
      res.end('500 - Internal server error')
    }
  }
}).listen(3000, () => console.log('Listening on localhost:3000'))

:arrow_up::arrow_up::arrow_up: Go back to summary

Url generation

import Router from 'instant-router';

const router = new Router();

// Name the routes to use UrlGenerator
router.addNamedRoute('comment', {
  path: '/posts/:postId/comments/:commentId',
  methods: ['GET'], // default
  controller: () => {}
})

const url = router.generateUrl('comment', 
  { 
    postId: 1, 
    commentId: 10, 
    foo: 1 // This is not a route parameter, so it will be added to the url as a query param.
  },
  { 
    isAbsolute: true,
    // scheme: 'http',
    // host: 'localhost',
    // port: 3000
  }
)

console.log(url); // "http://localhost:3000/posts/1/comments/10?foo=1"

:arrow_up::arrow_up::arrow_up: Go back to summary

Router class

Adding routes

To add a route, you must use the addRoute method.

Example:

router.addRoute({
  path: '/users/:id',
  methods: ['GET'],
  requirements: { name: '\\d+' },
  controller: ({ res }: HttpContext) => res.end("Matched!")
})

path

  • Should be a string that represents the URL path for the route.
  • Can contain parameters in the format of /my-path/:parameterName.
  • Parameters in the URL path can be constrained by a regex using the requirements property.

methods

  • Is an optional string or an array of strings that represents the HTTP methods. Default: GET
  • Valid methods are defined automatically via the route configuration or explicitly using the addMethods method.

requirements

  • Is an optional object that defines validation requirements for parameters in the URL path.
  • Keys (parameter name) and values (regex) must be of type string.

:warning: The router automatically adds the start and end delimiters, do not add them by yourself.

Bad: "^\\d+$", good: "\\d+"

controller

  • Should be a function that represents the handler for the route.
  • Take HttpContext ({req, res, params}) as an argument. Note that it will be your responsibility to call the controller.

:arrow_up::arrow_up::arrow_up: Go back to summary

Matching

To try to match a route from an HTTP request, you must use the match method.

Example:

const context = new RequestContext('/users/1', 'GET')
const { controller, params } = router.match(context);

The RequestContext class help you to pass relevant HTTP request data to the match method. You can read its documentation here.

The match method returns a MatchedTreeNode containing the properties controller and params.

Errors

  • If the request HTTP method does not match any existing route, a MethodNotAllowedError is triggered.
  • If no route is matched, a ResourceNotFound error is triggered.

See Usage for examples.

:arrow_up::arrow_up::arrow_up: Go back to summary

Generate urls

To generate urls, you can use the generateUrl method.

Example:

// Name the routes to use UrlGenerator
router.addNamedRoute('comment', {
  path: '/posts/:postId/comments/:commentId',
  methods: ['GET'],
  controller: () => {}
})

const url = router.generateUrl('comment', 
  { postId: 1, commentId: 10 }, 
  { isAbsolute: true }
)

name

Is the name of the route to generate the URL for.

parameters

Optional object containing key-value pairs of route parameter names. If a passed parameter does not match any route parameter, it is added to the url as a query parameter.

options

type urlGeneratorOptions = {
  isAbsolute?: boolean
  scheme?: string
  host?: string
  port?: number
}

You can set the default options directly when you instantiate Router:

const options = {
  urlGenerator: {
    isAbsolute: true,
    scheme: 'https', // must be defined if isAbsolute is true
    host: 'example.com', // must be defined if isAbsolute is true
    port: 443 // optional even if isAbsolute is true
  }
};

const router = new Router(options);

:arrow_up::arrow_up::arrow_up: Go back to summary

RequestContext class

The RequestContext class help you to pass relevant HTTP request data to the match method.

Usage:

import http from 'node:http';
import { RequestContext } from 'instant-router'

http.createServer((req, res) => {
    const context = RequestContext.fromIncomingMessage(req)

    console.log(context.path) // "/users/1"
    console.log(context.method) // "POST"
})

By default, RequestContext supports only the following http methods:

GET, HEAD, POST, PUT, DELETE, CONNECT, OPTIONS, TRACE, PATCH

But you can add more freely via the static property availableHttpMethods:

RequestContext.availableHttpMethods.push("FOO")

const context = new RequestContext("/hello", "FOO")
console.log(context.method) // "FOO"

:arrow_up::arrow_up::arrow_up: Go back to summary

Benchmarks

Benchmark comparisons, adapted from: https://github.com/delvedor/router-benchmark/tree/master/benchmarks

Machine

linux x64 | 8 vCPUs | 7.6GB Mem

Software versions

  • node: 18.14.2
=====================
 instant-router benchmark
=====================
short static: 232,598,222 ops/sec
static with same radix: 56,142,144 ops/sec
dynamic route: 3,162,154 ops/sec
mixed static dynamic: 3,150,744 ops/sec
long static: 55,622,346 ops/sec
all together: 1,380,149 ops/sec

=======================
 find-my-way benchmark
=======================
short static: 17,099,450 ops/sec
static with same radix: 6,243,196 ops/sec
dynamic route: 3,293,502 ops/sec
mixed static dynamic: 4,176,557 ops/sec
long static: 3,863,775 ops/sec
all together: 876,926 ops/sec

=======================================================
 express router benchmark (WARNING: includes handling)
=======================================================
short static: 2,111,590 ops/sec
static with same radix: 1,832,877 ops/sec
dynamic route: 1,087,600 ops/sec
mixed static dynamic: 831,342 ops/sec
long static: 860,493 ops/sec
all together: 221,828 ops/sec

:arrow_up::arrow_up::arrow_up: Go back to summary

1.0.1

12 months ago

1.0.0

12 months ago

0.1.6

1 year ago

0.1.5

1 year ago

0.1.4

1 year ago

0.1.3

1 year ago

0.1.2

1 year ago

0.1.1

1 year ago

0.1.0

1 year ago

0.0.11

1 year ago

0.0.10

1 year ago

0.0.9

1 year ago

0.0.7

1 year ago

0.0.6

1 year ago

0.0.5

1 year ago

0.0.4

1 year ago

0.0.3

1 year ago

0.0.2

1 year ago

0.0.1

1 year ago