Code Monkey home page Code Monkey logo

meta-router's Introduction

meta-router

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.

Example

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', authMiddleware({ redirect: true}), require('./pages/account'));

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:

[
    {
        "path": "GET /account",
        "handler": "require:./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("routes.json"));

// Apply security (if applicable)
app.use(require('my-auth-middleware'));

// Invoke the page handler (if applicable)
app.use(require('meta-router/middleware').invokeHandler());

Installation

npm install meta-router --save

Basic Usage

The basic usage is 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!');
        }
    }
]);

// 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 foo: ', route.config.foo);       // e.g. "bar"
    }
    next();
});

// Invoke the route handler (if available) and end the response:
app.use(require('meta-router/middleware').invokeHandler());

The API is described in more detail in the next section.

API

match() middleware

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("routes.json"));

Then in routes.json:

[
    {
        "path": "GET /users/:user", // HTTP method and path
        "handler": "require:./path/to/user/handler/module", // Path to a module that exports a route handler function
        "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:

  • JavaScript comments are allowed in the JSON configuration file (they are stripped out before parsing)
  • shortstop is used to preprocess the loaded JSON file to resolve handler functions and middleware. All of the handlers provided by shortstop-handlers are registered.
  • The require: handler supports resolving to a module method using the following syntax: "require:./some-module#someMethod"

invokeHandler() middleware

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.

buildMatcher(routes)

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'

getRoutes()

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" ]
  }
]

TODO

  • Add support for beforeHandler and afterHandler functions for each route

Maintainers

Contribute

Pull requests, bug reports and feature requests welcome. To run tests:

npm install
npm test

License

ISC

meta-router's People

Contributors

patrick-steele-idem avatar rragan avatar skoranga avatar viviangledhill avatar

Watchers

 avatar

Recommend Projects

  • React photo React

    A declarative, efficient, and flexible JavaScript library for building user interfaces.

  • Vue.js photo Vue.js

    🖖 Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.

  • Typescript photo Typescript

    TypeScript is a superset of JavaScript that compiles to clean JavaScript output.

  • TensorFlow photo TensorFlow

    An Open Source Machine Learning Framework for Everyone

  • Django photo Django

    The Web framework for perfectionists with deadlines.

  • D3 photo D3

    Bring data to life with SVG, Canvas and HTML. 📊📈🎉

Recommend Topics

  • javascript

    JavaScript (JS) is a lightweight interpreted programming language with first-class functions.

  • web

    Some thing interesting about web. New door for the world.

  • server

    A server is a program made to process requests and deliver data to clients.

  • Machine learning

    Machine learning is a way of modeling and interpreting data that allows a piece of software to respond intelligently.

  • Game

    Some thing interesting about game, make everyone happy.

Recommend Org

  • Facebook photo Facebook

    We are working to build community through open source technology. NB: members must have two-factor auth.

  • Microsoft photo Microsoft

    Open source projects and samples from Microsoft.

  • Google photo Google

    Google ❤️ Open Source for everyone.

  • D3 photo D3

    Data-Driven Documents codes.