A simple isomorphic router
Routing is the process of determining what code to run when a URL is requested.
router is a JS router, which works client-side (browser), or server-side (NodeJS).
router resolves URL paths like /profile/1 to route patterns such as /profile/:id, and generates a params object that is passed to each route.
router also provides some basic web server features if routing HTTP requests - parses req.body
into params, uses expressjs-like API, supports middleware.
- Easy setup, zero dependencies
- Only 2kb minified and gzipped
- Simple syntax and usage
- Works client-side, in browsers:
- as a router for single page applications (SPAs)
- Works server-side, in Node:
- as a router for an HTTP server (express.js like API, also supports "middleware")
- as a router in an AWS Lambda (routing of the
event
data passed into Lambda)
- Works in the terminal as an args parser for command-line programs
IMPORTANT! You must use the name
router
, and not something else.
router({
'/home': (params) => { ... },
'/profile/:id': (params) => { ... },
'/profile/:id/user/:uid': (params) => { ... },
});
The supported route pattern types are:
- static (
/users
) - named parameters (
/users/:id
) - nested parameters (
/users/:id/books/:title
) - optional parameters (
/posts(/:slug)
) - any match / wildcards (
/users/*
)
<script src="https://unpkg.com/@scottjarvis/router"></script>
<script>
// use router here
</script>
npm i @scottjarvis/router
Then add it to your project:
router = require('@scottjarvis/router');
// use router here
For routing frontend JS stuff like "single page applications", you'll need to trigger the router when the URL changes, or a link is clicked.
router is designed to be used with window.location.hash
and hashchange
.
Here' an example of how to bind the router to URL changes in the browser:
// 1. capture link clicks for links such as <a href="#/profile/1">some text</a>
window.addEventListener(
"click",
function handleLink(e) {
if (!e.target.matches("a")) return
if (!e.target.href.matches(/^#/)) return
e.preventDefault()
location.hash = e.target.hash
},
false
)
// 2. monitor changes to window.location.hash, and run the router when it changes
window.addEventListener(
"hashchange",
function(e) {
router.href(window.location.hash)
},
false
)
See the full example in examples/client-side-router.html
You could simply use router
inside a standard NodeJS http
server, with it's provided methods res.writeHead()
, res.write()
and res.end()
(see this nice guide to the NodeJS http
module).
However, router
provides some simple wrappers around these methods, just like express.js.
Here's an example of routing HTTP requests in your NodeJS based web server:
var http = require("http")
var router = require("router")
http.createServer((req, res) => {
router(
{
"/home": params => {
console.log("home!", params)
// set header status to "200",
// set content-type to "text/html",
// set content, end response
res.send("<p>some string</p>")
},
"/user/:userId": params => {
console.log("serving JSON!", params)
// set header to "200" manually
// set content-type to "application/json",
// set content (prettified JSON)
// end response
res.status(200)
res.send(params)
},
// any other route
"*": params => {
res.send("<h1>API Docs:</h1>")
}
},
// for servers, you must pass in 'req' and 'res', after the routes object above
req, res
)
})
There is a res.status()
method, which sets res.statusCode
for you.
There is a res.send()
method, which makes life easier for you:
- sets appropriate header status to 200 (if
res.status()
not used) - sets appropriate content type:
text/html
- if given a stringapplication/json
- if given an object, array or JSONapplication/octet-stream
- if given a Buffer
- pretty prints JSON output
- calls
res.end()
for you
The res.json()
method is similar to above, but sends the Content-Type application/json
.
The res.jsonp()
is similar to res.json()
, but sends the Content-Type text/javascript
and wraps your JSON in a callback, like so:
callback({ some: \"data\" })
If running an HTTP server (or Lambda, see below), router
supports "middleware", in a similar way to express.js.
Some express middleware may work with router
, though this has not been tested.
Creating middleware for router
is very simple - define a function that takes (req, res, next)
as parameters:
var getRequestTime = function(req, res, next) {
req.time = Date.now()
console.log("middleware: added req.time: ", req.time)
next()
}
And do router.use(someFunc)
to enable it:
// pass the middleware function to router.use()
router.use(getRequestTime)
Or enable middleware for specific routes:
router.use("/home", getRequestTime)
You can also pass an array of middlewares:
router.use([func1, func2, func3])
Or any array of middlewares to run on a specific route:
router.use('/home', [func1, func2, func3])
See the full example in examples/http-router.js
In NodeJS HTTP servers, the HTTP request "body" is received in "chunks" - you must normally combine & parse these chunks in order to get access to the whole req.body
data.
So to make life easier, router
does this basic parsing of the HTTP request body
for you, so that it's readily available in the params
passed to your routes:
- The
req.body
chunks received are combined into a string, available asreq.body
in your routes. - If
req.body
is a URL-encoded or JSON-encoded string,router
will convert it to a JS object, and also add its properties toparams
. For example, the originalreq.body
may be?user=bob&id=1
- this will be parsed for you and available asparams.user
andparams.id
.
Therefore, when inside your routes, there's often no need to parse req.body
yourself - unless handling gzipped data or file uploads (multi-part form data or octect-streams). In this case, you should use middleware like body-parser
.
If you're running a GET-based restful API, you probably don't need to worry about req.body
, it's usually only for POST data and file uploads.
Here's an example of using router
in an AWS Lambda:
'use strict';
var router = require("@scottjarvis/router")
exports.handler = (event, context, callback) => {
router(
{
"/ping": params => {
// do stuff here
callback(null, params)
},
"/user/:userId": params => {
// do stuff here
var resp = { ... }
callback(null, resp)
}
},
// for Lambdas, you must pass in 'event', 'context' and 'callback'
event,
context,
callback
)
}
In Lambdas, router
works out which route to run from the event.path
property (not from any HTTP req
objects).
To make life easier inside your routes:
- If
event.body
is URL-encoded or JSON-encoded, it'll be parsed into a JavaScript object. - If
event.body
was parsed into a JavaScript object, its properties will be added intoparams
. - The
params
object should have everything needed for a valid response object, so it can be passed straight tocallback()
(or returned, if running in anasync
Lambda).
There is currently very basic middleware support for Lambdas:
- Lambda middleware functions take
(event, next)
as parameters - so you should read or modify
event
, instead ofreq
andres
- it should otherwise be similar to using HTTP middleware :)
Here's how to define some Lambda middleware:
var getRequestTime = function(event, next) {
event.time = Date.now()
console.log("middleware: added event.time: ", event.time)
next()
}
And just pass the middleware function to router.use()
:
router.use(getRequestTime)
If you need a more advanced Lambda router, see middy.
See the full example in examples/lambda-router.js.
If you building a NodeJS program, you might want an easy way to parse the command line arguments it receives.
If so, router
can help - it auto maps command-line arguments to the params
object received by your routes:
node some_script.js --foo=bar --verbose --dir=/home
will be matched in some_script.js
by using something like:
var router = require("../src/router.js")
router({
// 'params' will contain all command-line arguments
// that were passed to this script
"*": params => {
console.log(params) // { foo: "bar", verbose: true, dir: "/home" }
}
})
See the full example in examples/cli-router.js.
Look in src/router.js
, make any changes you like.
Rebuild the bundles in dist/
using this command: npm run build
- director - a fairly small, isomorphic URL router for JavaScript
- hasher - Tiny hashchange router inspired by express.js & page.js
- routie - a tiny javascript hash router
- trouter - a fast, small-but-mighty, familiar router
- RouterRouter - a tiny JS router, extracted from Backbone's Router
- gcpantazis/router.js - a very simple router based on BackboneJS Router
- expressJS - the most widely used JavaScript server, with routing "middleware"
- polka - minimal, performant expressjs alternative (uses trouter)
- aws-lambda-router - nice, simple AWS router, good feature set
- middy - a popular but complex router for AWS Lambda, uses a middleware-style API
- Zetcode: javascript/http
- @pyaesonekhant1234: differences-between-res-write-res-end-and-res-send
- Okta.com: Build and understand express middleware through examples
- Towards Data Science: Building your own router for AWS Lambda
- vkhazin/aws-lambda-http-router
See Issues
Some intended future improvements are at https://github.com/sc0ttj/router/issues
A middleware that adds the same properties and methods to req
and res
as express.js.
This should improve compatibility with other express.js middleware that is loaded after.
See