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 🙏

© 2024 – Pkg Stats / Ryan Hefner

custom-router

v1.0.4

Published

Customizable request router

Downloads

17

Readme

custom-router

This is a Node.js router which does not include (or require) an HTTP server. It is meant to match request data passed to it from any HTTP server you wish with configured routes and triggers callbacks. A good example of where routing is not built-in is AWS Lambda, in which case custom-router works perfectly as a proxy for an AWS Lambda handler.

Configuration

The router makes no assumptions about what kind of request object you're using. It acts more like a proxy for your original request. You simply need to specify a function which returns the method and path that routes can be matched against. (Default matching is provided by route-parser.)

This means you could use Express, Koa or some other web server request such as AWS Lambda.

Request Resolver

The router can work with any request/response you need.

A typical HTTP server (e.g. Express.js) may supply a request and response like this:

import router from "custom-router"

// Simply return the `method` and `path`. You could potentially return the request object directly if it already has those properties.
// NOTE: the arguments passed here will also be made available to each route callback (see below)
const resolver = (req, _res) => {
  return {
    method: req.method,
    path: req.path,
    // NOTE: any additional data is passed as part of the `context` argument in your route callback
  }
}

In an HTTP server where you have a request/response object model, you may need access to the response for your route to render a response rather than passing back arbitrary data.

const r = router(resolver)

r.get('/path/:param', (params, { method, path, ...data }, req, res) => {
  // All arguments passed to the resolver are also passed to the route callback.
  // You can modify the response as needed based on this route.
  res.body = JSON.stringify({
    data: 'some data'
  })
})

// In a request/response object model, you'd integrate this router at some point when there's
// a request/response. You can use `handle` to handle any arguments, as long as your resolver knows
// what to do with them to return `method` and `path`. Then those arguments are also passed to the routes.
const async (req, res) => r.handle(req, res)

We'll continue our examples using an AWS Lambda example:

// AWS Lambda handlers receive an event, context and callback, and we return the `method` and `path`
const resolver = (event, _context, _callback) => {
  return {
    method: event.httpMethod,
    path: event.path,
    // NOTE: any additional data is passed as part of the `context` argument in your route callback
  }
}

// Instantiate a router w/ any function which returns `method` and `path`.
// (You can pass back any other data with it, which will be available to your route callbacks.)
const r = router(resolver)


r.get('/some-path/:param', (params, context, ...request) => {
  // params matched from the path
  const { param } = params
  // data returned by the function passed to the router
  const { method, path, ...rest } = context
  // all arguments passed to the function which returned the context data
  const [ _event, _context, _callback ] = request
})

// `handle` accepts the incoming request like any callback in your server would.
// AWS Lambda can use this directly to accept the `event`, `context` and `callback` params.
// Let's assume we're creating an AWS Lambda handler.
export const async (event, context) => r.handle(event, context)

Route Path Matchers

Each HTTP method can be matched. All matchers are async and return a Promise.

import router from "custom-router"

// ... define your `resolver`

const r = router(resolver)

// Specify one or more HTTP methods
r.match('/path-one', { method: 'GET' }, () => { /* response */ })
r.match('/path-two', { method: ['GET', 'POST'] }, () => { /* response */ })
// or
r.get(/* ... */)
r.post(/* ... */)
r.put(/* ... */)
r.delete(/* ... */)
r.options(/* ... */)

// No method matches *any* HTTP method
r.match('/path-three', () => { /* response */ })
// or
r.any(/* ... */)

You can match some special cases.

// specify a callback for any unknown route path
r.unknown((context, ...request) => {
  // Receives the same arguments as a route callback, except no path params because there was no match.
})

// specify a callback for any exception
r.error((error, params, context, ...request) => {
  // Receives the same arguments as a route callback, with the exception as the additional first argument.
})

Route Path Options

Each route matcher has options for defaults and constraints in addition to method.

// You can specify a function for defaults
const defaultsFunction = params => { /* return default values (don't modify `params`) */ }

// You can specify an object
const defaultsObject = { /* whatever you want, which will get merged in to `params` */ }

// You can specify a function to validate constraints
const constraintsFunction = params => { /* throw an exception if params are invalid */ }

// You can specify an object with each key matching a route param.
// NOTE: only keys which are present in the params are matched (optional params may not be present)
const constraintsObject = {
  // RegExp to match specific pattern
  param1: /^\d+$/,
  // Array to match whitelisted values
  param2: ['A', 'B'],
  // Function for custom validation
  param3: (value, _key) => !!value,
  // Boolean to match truthy or falsy values
  param4: true
}

// Options can be either one
const options = {
  defaults: defaultsObject,
  constraints: constraintsObject
}

r.get('/some-path/:param1/:param2(/:param3/:param4)', options, () => { /* ... */ })

Route Path Resolver

The default path matcher (RouteResolver) uses route-parser internally. You can specify a custom path matcher, which needs to respond to build and match.

const resolver = {
  // Used in case you need to compile your parser.
  // Must return an instance of a `Route`.
  // * `spec` is the path pattern such as "/path/:param"
  build (spec, callback, options) {
    // build your `spec` parser
    return new Route(spec, callback, options)
  },

  // Must return a params object (can be empty) or `null` if not matched.
  // * `spec` is the path pattern such as "/path/:param"
  // * `path` is the actual request path such as "/users/username"
  match (spec, path) {
    // totally contrived example:
    if (spec === '/path/:param' && path === '/users/username') {
      return {
        param: 'username'
      }
    } else {
      return null
    }
  }
}