Home

Awesome

Rotunda

Rotunda is a modern promise-based isomorphic routing library for Node.js and the browser inspired by the express framework and Django.

license - MIT Dependencies

NPM status

Build status Coverage status

Install

Note: Rotunda uses language features that were introduced in ES2015 (ES6). The code has been converted to ES5 syntax using Babel but expects certain globals like Promise and Map that may not be available in older JavaScript environments. If you plan to use Rotunda in older browsers or older versions of Node.js you should use a polyfill/shim library like core-js.

With NPM

npm install rotunda

From source

git clone https://github.com/foss-haas/rotunda.git
cd rotunda
npm install
npm run dist

In order to run the automated type checks for development, you will need to have flow installed and available from the project folder. If flow is not available, the type checks will be skipped temporarily.

API

new Router

Creates a Router instance.

Arguments

Examples

import Router from 'rotunda';
let router = new Router();
let caselessRouter = new Router(true);

// ES5 equivalent:

var Router = require('rotunda').Router;
var router = new Router();
var caselessRouter = new Router(true);

Router#param

Defines a named parameter on the router. Returns the router instance itself to allow chaining.

Arguments

If you only want to assign a schema to the parameter, you can pass the schema as the second argument.

If neither resolve nor schema are specified, the method has no effect.

If both are defined, the value will first be validated against the schema and then passed to the resolve function.

Examples

// Let's use joi for our schemas
import joi from 'joi';

// Define a parameter that resolves immediately
router.param('primeNumber', function (value) {
  // Joi has validated the value and converted it to a number
  // So we can just pass it to other code that expects a number
  if (isPrimeNumber(value)) return value;
  // Not a prime, probably the wrong route
  // Reject without reason to try the next route instead
  return Promise.reject();
}, joi.number().integer());

// Define a parameter that resolves asynchronously
router.param('articleId', function (value) {
  // Let's make some kind of remote API call over AJAX with the validated ID
  return ajax.get(`/api/articles/${value}`);
}, joi.number().integer());

// Define a parameter that depends on another parameter
router.param('userArticleId', function (value, params) {
  return params.userId
  .then(function (validUserId) {
    // We have waited for the "userId" parameter to be resolved
    // Now let's do something that returns a promise
    return ajax.get(`/api/users/${validUserId}/articles/${value}`);
  });
}, joi.number().integer());

// Define a parameter with only a resolve function
router.param('magic', function (value) {
  return ajax.post('/api/magic', {magic: value})
  .then(
    function (magic) {
      return magic * 2;
    },
    function (apiError) {
      // Reject with a reason to abort the routing
      return Promise.reject({
        error: 'Out of magic!',
        reason: apiError
      });
    }
  )
});

// Define a parameter with only a schema
router.param('someNumber', joi.number());

// This has no effect
router.param('nothing');

Router#route

Defines a route on the router. Returns the router instance itself to allow chaining.

Arguments

Examples

router.route('/users/:userId', function (params) {
  return Promise.resolve(`This is the user page for the User #${params.userId}!`);
});

// Non-promise return values will be wrapped automatically
router.route('/articles/:articleId', function (params) {
  return `This is the article page for Article #${params.userId}!`;
});

// Parameters will have been resolved before the route handler is invoked
router.param('comment', function (value) {
  return ajax.get(`/api/comments/${value}`);
}, joi.number().integer().required());
router.route('/articles/:articleId/comments/:comment', function (params) {
  return `Comment: ${params.comment.title} by ${params.comment.author}`;
});

// The raw values of parameters are available, too
router.route('/articles/:articleId/comments/:comment', function (params) {
  var raw = params.$raw;
  return `URL: /articles/${raw.articleId}/comments/${raw.comment}`;
});

Router#reverse

Returns a path that would resolve to the route name and parameters.

Arguments

Examples

router.param('articleId', joi.number().integer());
router.route('/articles/:articleId', function () {/*...*/}, 'article_detail');

// You can always pass in parameter values as strings
router.reverse('article_detail', {articleId: '23'});
// -> "/articles/23"

// You can also pass in non-string values
router.reverse('article_detail', {articleId: 42});
// -> "/articles/42"

// But be wary of passing in arbitrary objects
router.reverse('article_detail', {articleId: {some: 'object'}});
// -> "/articles/[object Object]"

// You always have to pass in all parameters
router.reverse('article_detail', {articleId: '23'});
// -> Error: Failed to reverse article_detail. Missing param: articleId

// Extra parameters will be ignored
router.reverse('article_detail', {articleId: '23', size: 'xxl'});
// -> "/articles/23"

Router#resolve

Attempts to resolve a path. Returns a promise that is rejected if the path does not successfully match any routes or resolved with the matching route handler's result.

Arguments

Examples

router.param('articleId', joi.number().integer());
router.route('/articles/:articleId', function (params) {
  return `This is the article page for Article #${params.userId}!`;
});

router.resolve('/articles/23').then(
  function (result) {console.log(result);},
  function (err) {console.error(err);}
);
// -> This is the article page for Article #23

// Paths that don't match anything are rejected
router.resolve('/articles/pants').then(
  function (result) {console.log(result);},
  function (err) {console.error(err);}
);
// -> Error: 404

// Paths that match a route that is rejected with a reason are rejected
router.route('/bad-route', function () {
  return Promise.reject(new Error('Out of order'));
});
router.resolve('/bad-route').then(
  function (result) {console.log(result);},
  function (err) {console.error(err);}
);
// -> Error: Out of order

// Parameters that are rejected with a reason also result in rejection
router.param('bad-param', function () {
  return Promise.reject(new Error('Server error'));
});
router.route('/some-route/bad-param', function () {/*never reached*/});
router.resolve('/some-route/bad-param').then(
  function (result) {console.log(result);},
  function (err) {console.error(err);}
);
// -> Error: Server error

// Contexts can be used to pass run-time dependencies to a route
router.param('article', function (value, params, context) {
  return context.api.getArticle(value);
});
router.route('/articles/:article', function (params, context) {
  return `This is the article page for article #${params.article.title}`;
});
router.resolve('/articles/23', {api: require('./my-api')}).then(
  function (result) {console.log(result);},
  function (err) {console.error(err);}
);

License

The MIT/Expat license. For more information, see http://foss-haas.mit-license.org/ or the accompanying LICENSE file.