monorouter is an isomorphic JavaScript router by @matthewwithanm and @lettertwo. It was designed for use with ReactJS but doesn't have any direct dependencies on it and should be easily adaptable to other virtual DOM libraries.
While it can be used for both browser-only and server-only routing, it was designed from the ground up to be able to route apps on both sides of the wire.
Note: This project is in beta and we consider the API in flux. Let us know if you have any ideas for improvement!
Defining a router looks like this:
var monorouter = require('monorouter');
var reactRouting = require('monorouter-react');
monorouter()
.setup(reactRouting())
.route('/', function(req) {
this.render(MyView);
})
.route('/pets/:name/', function(req) {
this.render(PetView, {petName: req.params.name});
});
Here, we're simply rendering views for two different routes. With monorouter, a "view" is any function that returns a DOM descriptor.
The router can be used on the server with express and connect-monorouter:
var express = require('express');
var router = require('./path/to/my/router');
var monorouterMiddleware = require('connect-monorouter');
var app = express();
app.use(monorouterMiddleware(router));
var server = app.listen(3000, function() {
console.log('Listening on port %d', server.address().port);
});
And in the browser:
router
.attach(document)
.captureClicks(); // This is optional—it uses the router to handle normal links.
See the examples for a more in-depth look and more tricks!
Error handling with monorouter is similar to Express—just add a middleware with an arity of 3:
router.use(function(err, req, next) {
if (err.status === 404) {
this.render(My404Template);
}
});
In addition to the router itself, monorouter has two important objects. The
first one is the request object, and it's passed as the first argument to your
handler. The second is the context (this
) of your route handler, and it's used
to interface with your application's state (you can think of this as being
similar to a Response object in server-only routers). This section summarizes
their APIs.
param(name:String): Get the value for one of the dynamic parts of your route:
monorouter()
.route('/pets/:name', function(req) {
console.log(req.param('name'));
// snip
});
params:Object: A hash of the params used in your route.
canceled:Boolean: A boolean that represents whether the request has been canceled. This is useful for preventing further action in async callbacks:
monorouter()
.route('/', function(req) {
http('...', function(err, result) {
if (req.canceled) return;
this.render(MyView, {person: result.people[0]});
}.bind(this));
});
Note that it's not necessary to check this value if all you're doing is rendering since those operations are no-ops when the request has been canceled. The request is also an EventEmitter that emits a "cancel" event so, if you'd like to take action immediately when a request is canceled (and abort an XHR request, for example), you can do that:
monorouter()
.route('/', function(req) {
var xhr = http('...', function(err, result) {
if (req.canceled) return;
this.render(MyView, {person: result.people[0]});
}.bind(this));
this.on('cancel', function() {
xhr.abort();
});
});
initialOnly:Boolean: Indicates whether the request is only for the initial
state of the app. true
when rendering on the server.
location:Object: A parsed version of the requested URL, in a format based
on the document.location
interface.
url:String: The requested URL.
protocol:String: The protocol of the requested URL, without the colon.
e.g. "http"
hostname:String: The hostname of the requested URL, e.g. 'mysite.com'
host:String: The full host of the requested URL, e.g. 'mysite.com:5000'
search:String: The search portion of the requested URL, including the
question mark, e.g. '?hello=5&goodbye=a'
querystring:String: The search portion of the requested URL, excluding the
question mark, e.g. 'hello=5&goodbye=a'
query:Object: A version of the query string that's been parsed using @sindresorhus's query-string.
hash:String: The hash portion of the requested URL, including the hash
mark. e.g. '#this-is-the-hash'
fragment:String: The hash portion of the requested URL, excluding the hash
mark. e.g. 'this-is-the-hash'
first:Boolean: Is this the first request being handled by this router?
from(causes:String...):Boolean: Check the cause of the request. Causes that monorouter sends are:
"startup"
- When a request is triggered by the router initialization in"popstate"
- When a request is triggered by a popstate event"link"
- When a request is triggered by a link click captured by the linkYou may also send custom causes. For example, connect-monorouter uses the string '"httprequest"'. Generally, causes shouldn't be used to affect routing behavior—they are meant primarily for logging.
Within a route handler, you use properties and methods of this
to define the
application state. Here are some of those:
view
is a function that returns asetView
(e.g. in middleware). "vars" are arguments for thisrender
, but doesn't end the request. This is useful if you'd like to renderrender
, but only ends "initialOnly" requests.render*
'text/html; charset=utf-8'
If the original idea for react-nested-router was "Ember got it mostly right," monorouter's can be said to be "Express got it mostly right." (Or koa. Or Flask. Or Django. Or Rails…) Server-side routing is very easy: a request comes in and, in response, you render a template. Each time this happens, you render the entire document. Until recently, this approach seemed incongruent with client-side rendering, but ReactJS and the virtual DOM have changed that.
Still, client-side routing is fundamentally different than server-side in some ways—most notably in that state can be shared between routes. monorouter aims to expose the functionality related to GUI routing that's common to the server and browser. Some principles of the project are:
<head>
s