
Security News
The Changelog Podcast: Practical Steps to Stay Safe on npm
Learn the essential steps every developer should take to stay secure on npm and reduce exposure to supply chain attacks.
meta-router
Advanced tools
Declarative URL router for Express that provides support for associating metadata with a route.
This simple, declarative URL router provides Express middleware that can be used to associate metadata with a route. In addition, this module allows an incoming request to be matched to a route at the beginning of the request and allows the handling of the request to be deferred to later in the request. This is helpful in many applications, because intermediate middleware can use the metadata associated with the matched route to conditionally apply security checks, tracking, additional debugging, etc.
Internally, this module utilizes the same module used by Express to parse and match URLs—thus providing an easy transition from the builtin Express router to this router. The router also exposes an API that can be used independent of Express to match a path to route.
Let's say that you want to register authentication middleware for an application, but only a few of the routes actually require authentication. One option is to register the route-specific authentication middleware for each route using code similar to the following:
var authMiddleware = require('my-auth-middleware');
app.get('/account' /* Route path */,
authMiddleware({ redirect: true}) /* Route-specific middleware */,
require('./pages/account') /* Handler: function(req, res, next) { ... } */);
While the above code will work as expected, it has a few drawbacks. The extra route-specific middleware adds clutter and the resulting code is not declarative.
To solve these problems, let's move our routes to a JSON file:
[
{
"route": "GET /account => ./pages/account",
"security": {
"authenticationRequired": true,
"redirect": true
}
}
]
By itself, the "security" metadata for the declared route will have no impact. To enforce authentication we can change the implementation of my-auth-middleware to be similar to the following:
module.exports = function(req, res, next) {
var routeConfig = req.route && req.route.config;
if (routeConfig.security && routeConfig.security.authenticationRequired) {
if (isUserAuthenticated(req)) {
next();
} else {
// Handle un-authenticated user...
}
} else {
// Route has no security policy... just continue on...
next();
}
}
Finally, to tie everything together we need to register the following middleware (order matters):
// Match the incoming request to a route:
app.use(require('meta-router/middleware').match("/path/to/routes.json"));
// Apply security (if applicable)
app.use(require('my-auth-middleware'));
// Invoke the route handler (if applicable)
app.use(require('meta-router/middleware').invokeHandler());
npm install meta-router --save
routes.json:
[
"GET /users/:user => ./user",
"GET /login => ./user#login",
"GET /logout => ./user#logout",
"POST /users/:user/picture => ./user#uploadPicture"
]
Each route should be of the following format:
<HTTP_METHOD> <PATH> => <HANDLER_MODULE_PATH[#<HANDLER_METHOD>]>
HTTP_METHOD should be GET, POST, etc.PATH should be an Express-style route pathHANDLER_MODULE_PATH should be the relative path to a Node.js JavaScript moduleHANDLER_METHOD should be the name of a method on the handler moduleThe code to register the meta-router middleware is shown below:
// Match the incoming request to a route:
app.use(require('meta-router/middleware').match("/path/to/routes.json"));
// Intermediate middleware:
app.use(require('my-auth-middleware'));
// Finally, invoke the route handler (if applicable)
app.use(require('meta-router/middleware').invokeHandler());
Route metadata can either be inlined in the JSON routes file or it can be attached to the handler as shown below:
Option 1) Route metadata in JSON file:
[
{
"route": "GET /users/:user => ./user",
"security": {
"authenticationRequired": true,
"redirect": true
}
},
...
]
Option 2) Attach route metadata to handler function:
./user.js
function userHandler(req, res, next) {
// ...
}
userHandler.routeMeta = {
security: {
authenticationRequired: true,
redirect: true
}
}
// You can also optionally attach middleware to the route:
userHandler.routeMiddleware = [
function (req, res, next) {
// ...
},
// ...
]
module.exports = userHandler;
When using a route handler method (e.g. ./user#login), the JavaScript code to export the login handler method will be similar to the following:
./user.js
exports.login = function (req, res, next) {
// ...
};
exports.login.routeMeta = { /* ... */ }; // Optional route metadata
exports.login.routeMiddleware = { /* ... */ }; // Optional route-specific middleware
If you don't like JSON Files, you can also choose to configure the routes in your JavaScript code that registers the match middleware as shown below:
// Match the incoming request to one of the provided routes
app.use(require('meta-router/middleware').match([
{
path: 'GET /users/:user',
middleware: [ // Any number of middleware functions to use for this route (called right before handler)
function(req, res, next) {
if (isNotLoggedIn(req)) {
res.status(401).end('Not authorized');
} else {
next();
}
}
],
handler: function(req, res) {
res.end('Hello user: ' + req.params.user);
},
foo: 'bar' // <-- Arbitrary metadata
},
{
path: 'POST /users/:user/picture',
handler: function(req, res) {
saveProfilePicture(req);
res.end('User profile picture updated!');
}
}
]);
// Invoke the route handler (if available) and end the response:
app.use(require('meta-router/middleware').invokeHandler());
After the match middleware runs, the matched route information is stored in the req.route property. The information associated with matched route can be read as shown below:
// Use information from the matched route
app.use(function(req, res, next) {
var route = req.route;
if (route) {
console.log('Route params: ', route.params); // e.g. { user: 'John' }
console.log('Route path: ', route.path); // e.g. "/users/123"
console.log('Route path: ', route.config.path); // e.g. "/users/:user"
console.log('Route methods: ', route.config.methods); // e.g. ['GET']
console.log('Route meta: ', route.config.foo); // e.g. "bar"
}
next();
});
A special matchOptions property can be included with route metadata to control how the path matching regular expression is generated using path-to-regexp as shown below:
[
{
"route": "GET /users/:user => ./user",
"security": {
"authenticationRequired": true,
"redirect": true
},
"matchOptions": {
"sensitive": false,
"strict": false,
"end": true
}
},
...
]
Supported properties for matchOptions:
true the route will be case sensitive. (default: false)false the trailing slash is optional. (default: false)false the path will match at the beginning. (default: true)The match() middleware matches an incoming request to one of the possible routes. If an incoming request matched any routes passed in then the req.route property will be populated with information about the route.
require('meta-router/middleware').match(routes);
The routes argument can either be an Array of routes or a path to a JSON routes file (explained later). The format of the routes Array is explained by the following example below:
[
{
path: 'GET /users/:user', // HTTP method and path
handler: function(req, res) { // Route handler function
...
}, // Route handler function
middleware: [ // Route-specific middleware to run right before the handler
function foo(req, res, next) {
// ...
next();
},
function bar(req, res, next) {
// ...
next();
}
],
// Any additional metadata to associate with this route: (optional)
foo: 'bar',
},
// Alternatively, multiple HTTP methods can be matched
{
methods: ['GET', 'POST']
path: '/foo', // HTTP method and path
handler: ...,
...
},
// Or to match all HTTP methods, the method can be omitted altogether
{
path: '/bar', // HTTP method and path
handler: ...,
...
},
// Additional routes:
...
]
If a String is passed to the match() middleware function then it is treated as a path to a JSON routes file. For example:
app.use(require('meta-router/middleware').match(require.resolve('./routes.json'));
Then in ./routes.json:
[
{
"route": "GET /users/:user => ./path/to/user/handler/module", // HTTP method, path and handler
"middleware": [ // Route-specific middleware to run right before the handler (optional)
"require:foo", // Path to a module that exports a route handler function
{
"factory": "require:bar",
"method": "baz" // Optional name of a property name to lookup the actual factory
"arguments": [ // Optional arguments to use when calling the factory function
"hello",
"world"
]
}
],
// Any additional metadata to associate with this route (optional):
"foo": "bar"
},
...
]
A few things to note when using a JSON routes file:
require: handler supports resolving to a module method using the following syntax: "require:./some-module#someMethod"The invokeHandler() can be used to invoke a route handler associated with the matched route.
app.use(require('meta-router/middleware').invokeHandler());
If a route with a handler was matched, then the associated handler function will be invoked (passing in the req and res objects). The route handler is expected to end the response to complete the request. If no route was matched or if the route does not have an associated handler then the next middleware in the chain will be invoked.
Given an Array of routes, the buildMatcher(routes) method will return an object with a match(path[, method]) method that can be used to match a path or path+method to one of the provided routes.
Usage:
var matcher = require('meta-router').buildMatcher([
{
path: 'GET /users/:user',
handler: function(req, res) {
...
}
},
...
]);
var match = matcher.match('/users/123', 'GET');
// match.params.user === '123'
// match.config.path === '/users/:user'
Since the method argument is optional, the following is also supported:
var matcher = require('meta-router').buildMatcher([
{
path: '/users/:user',
handler: function(req, res) {
...
}
},
...
]);
var match = matcher.match('/users/123');
// match.params.user === '123'
// match.config.path === '/users/:user'
Returns summary information for the routes matchable by meta-router. Returned information is of the form:
[
{"path": "path of the route",
"methods": ["list of methods on the route"]}
]
For example,
[
{
"path":"/keywords",
"methods":[ "GET" ]
},
{
"path":"/update",
"methods":[ "GET", "POST" ]
},
{
"path":"/",
"methods":[ "GET" ]
}
]
meta-router/macroThe meta-router/macro can be used to load the routes config at compile time using a babel macro.
const loadRoutes = require("meta-router/macro");
app.use(require('meta-router/middleware').match(loadRoutes("./path/to/routes.json")));
beforeHandler and afterHandler functions for each routeSee CHANGELOG.md
Pull requests, bug reports and feature requests welcome. To run tests:
npm install
npm test
ISC
FAQs
Declarative URL router for Express that provides support for associating metadata with a route.
We found that meta-router demonstrated a not healthy version release cadence and project activity because the last version was released a year ago. It has 3 open source maintainers collaborating on the project.
Did you know?

Socket for GitHub automatically highlights issues in each pull request and monitors the health of all your open source dependencies. Discover the contents of your packages and block harmful activity before you install or update your dependencies.

Security News
Learn the essential steps every developer should take to stay secure on npm and reduce exposure to supply chain attacks.

Security News
Experts push back on new claims about AI-driven ransomware, warning that hype and sponsored research are distorting how the threat is understood.

Security News
Ruby's creator Matz assumes control of RubyGems and Bundler repositories while former maintainers agree to step back and transfer all rights to end the dispute.