detour

Resource-oriented routing: Route to objects instead of functions for better code-reuse and organization.

detour

Detour is a router for node.js web applications.

Detour is different from sinatra-style routers (like express's router) because you route urls to objects (that have http methods) instead of to http methods.

Rationale: If you have multiple http methods implemented for a given url (like a lot of APIs do), this style of routing will be much more natural and will vastly improve your code organization and re-use. With object routing, it's much simpler to keep the related handlers together, but separated from unrelated handlers (often even in another file/module).

It works for node.js' standard HTTP server, as well as express and connect applications.

  var detour = require('detour');
  var http   = require('http');
  var router = detour();
 
  router.route('/', {
    GET : function(reqres){
      res.end("GET works!");
    },
    POST : function(reqres){
      res.end("POST works too!");
    }
  });
 
  http.createServer(function(reqres){
    router.middleware(req, res);
  }).listen(9999);
  var detour = require('detour');
  var express = require('express');
  var app = express();
  var router = detour();
  app.use(router.middleware);
  
  router.route('/', {
    GET : function(reqres){
      res.end("GET works!");
    },
    POST : function(reqres){
      res.end("POST works too!");
    }
  });
 

A simple example:

  router.route('/test/:test_id', {
    GET : function(reqres){
      res.end("GET works: " + req.pathVar.test_id);
      // a request to /test/1234 will have output like this: 
      //   GET works: 1234 
      
      // req.pathVar is a hash of all name value pairs pull from the 
      // request url 
    },
  });
 

In the example above, you can see that the path string sent to the router (/test/:test_id) uses the same format as express. Regular expressions are also allowed.

You can also see from the example that within a route handler, req.pathVar will have all the variables collected from your route.

Detour supports connect-style middleware like most node.js frameworks (including express). Example:

  var detour = require('detour');
  var router = detour();
  router.use(someMiddleware);

The middleware stack for detour runs after the route has been determined, but before any other processing. The entire routed resource object will be available at req.detourHandler if you want to do any special handling in your middleware that are related to its contents.

This makes Detour extremely extensible on a resource-by-resource basis. For example, if you write a resource object like this:

  router.route('/test/:test_id', {
    mustBeAuthenticated : true,   // <-- NB: non-standard property 
    GET : function(reqres){
      res.end("GET works: " + req.pathVar.test_id);
    },
  });
 

...you can write a middleware that acts according to that data before the handler for GET is called.

In addition to regular object routing, For the sake of convenience, Detour also supports collection-routing where two resource objects can be routed in a collection-member relationship at the same time.

Here's an example:

var router = new Router();
router.collection('/test/:testid', {
  collection : {
    GET : function(reqres){
      res.end('collection here!');
    }
  },
  member : {
    GET : function(reqres){
      res.end('member here! ' + JSON.stringify(req.pathVar));
    }
  }
});

Note: the object being routed has two properties: collection and member, with each being a resource object on its own. The member resource object will be routed to the given path (/test/:testid in this case), and the collection resource object will be routed to the parent path (/test in this case).

This feature can be useful if you have resources that follow this pattern and you want to keep these two similar resources together in the same file.

There is a default OPTIONS response for any route but you can over-ride it like this:

router.on('OPTIONS', function(reqresresource){
  // resource is the object you routed.  You can loop through the methods on it here if you want! 
  res.writeHead(200);
  res.end("options, options, options!");
});

There is a default HEAD response for any route with a GET, but you can over-ride it like this:

router.on('HEAD', function(reqresresource){
  // resource is the object you routed.  You can call resource.GET() from here if you want! 
  res.writeHead(200);
  res.end("doesn't matter what I type here.  node will not send output on a HEAD request")
});

The correct http response for a method that an existing url doesn't support is a 405. You can over-ride the default like this:

router.on(405, function(reqresresource){
  // resource is the object you routed.  You can loop through the methods on it here if you want! 
  res.writeHead(405);
  res.end("this resource doesn't support that method! ");
});

There is a default 404 response for any route that doesn't match, but you can over-ride it like this:

router.on(404, function(reqres){
  res.writeHead(404);
  res.end("nope. couldn't find that!")
});

(NOTE: this only works if the middleware doesn't get a third parameter -- typically called next() -- passed to it, otherwise next() is used for 404s as you'd expect from a middleware.)