Express Map
Named routes and path lookups for Express that can be used on the server, in
the browser, and inside your templates. Never hard-code any URL paths in your
code ever again, or even share your routes between the browser and server!
Overview
Goals
While building a website, it's common to reference URL paths in multiple places,
whether it be inside a template, inside some redirection code, or even inside
a client-side router.
If you ever have to change a URL though, it can be difficult to go through
all of your code, and manually change all of the URLs that you hard-coded into
your templates, server-side code, and client-side code.
Express Map solves this problem, and has even more advanced use cases, if you're
considering building an single-page application that shares client-side routes
(using a client-side JavaScript framework like Backbone, the YUI App Framework,
or any other library that provides a client-side router) and server-side routes
together for progressive enhancement.
How It Works
Express Map takes advantage of the route annotation system provided by
Express Annotations to add
additional metadata to your routes.
Installation
Install using npm:
$ npm install express-map
Usage
Extending an Express App
To use Express Map with an Express app, the app must first be extended. Use the
extend()
method that Express Map exports:
var express = require('express'),
expmap = require('express-map'),
app = express();
expmap.extend(app);
Note: It's prefectly fine for the same Express app to be extended more than
once; after the first time the app is extended, the subsequent extend()
calls
will be noops.
Once extended, the app
object will contain three new methods as described
below.
Methods
app.map(path, name)
This function links together a particular URL path with a name that you provide,
that you'll use to reference that route everywhere else, so you don't need to
hard-code the URL path everywhere. An example:
app.map('/blog/', 'blog');
app.map('/blog/:post', 'blog-post');
app.annotate('/blog/', {section: 'blog'});
app.annotate('/blog/:post', {section: 'blog'});
app.get('/blog/', function (req, res) {
});
app.get('/blog/:post', function (req, res) {
});
A common technique to use is to create a sugar method that combines the actual
app.VERB()
route with app.map()
, though we kept this separate for more
flexibility in how you use Express Map. The above is functionally equivalent to:
function routePage(path, name, annotations, callback) {
app.map(path, name);
app.annotate(path, annotations);
app.get(path, callback);
}
routePage('/blog/', 'blog', {section: 'blog'}, function (req, res) {
});
routePage('/blog/:post', 'blog-post', {section: 'blog'}, function (req, res) {
});
This sets up the mapped route for you, which you'll use in the next functions.
Note: You can map the same route more than once with different names, or
provide an array as the name
in app.map
. This creates aliases, where
you can reference the path by any of the names later on, and it'll return
the same URL path. However, as we'll see later, each route only has one
canonical name
, that you can access as part of the route object.
app.getRouteMap([annotations])
This function returns the route map object, which contains the route metadata
serialized into an object that can be used anywhere. If we took the above
routes, the serialized object would look like this:
{
'blog': {
path : '/blog/',
keys : [],
regexp : /^\/blog\/\/?$/i,
annotations : {
name : 'blog',
aliases : ['blog'],
section : 'blog'
}
},
'blog-post': {
path : '/blog/:post',
keys : [{ name: 'post', optional: false }],
regexp : /^\/blog\/(?:([^\/]+?))\/?$/i,
annotations : {
name : 'blog-post',
aliases : ['blog-post'],
section : 'blog'
}
}
}
If no annotations
are passed in to getRouteMap
, then all routes are
returned. Otherwise, the returned route map will be filtered by the annotations
passed in. For instance:
var routeMap = app.getRouteMap({name: 'blog-post'});
Take a look at the test examples to see what other filters can be applied.
app.getRouteParams([routeMap])
This function returns a mapping of route parameter names to the functions used
in those route parameters created through the app.param()
API.
It takes in an optional parameter, routeMap
, which is the route map object
returned from app.getRouteMap()
. If provided, then getRouteParams()
will
only search through the routes available in the provided route map object.
Otherwise, it will search through all routes available in the application.
It can be used in the following way:
app.param('user', function (req, res, next, id) {
User.find(id, function (err, user) {
});
});
app.param('post', function (req, res, next, id) {
Post.find(id, function (err, post) {
});
});
var paramMap = app.getRouteParams();
Static Methods
expmap.pathTo(routeMap)
This static function on the Express Map instance (commonly expmap
) takes in
a route map object, and returns a function with the following signature:
function (routeName, [context])
This returned function is meant to take in a route name, and an optional context,
and return the contextualized URL path of that route.
For example, using our routes from before:
var routeMap = app.getRouteMap(),
pathTo = expmap.pathTo(routeMap);
pathTo('blog-post', {post: 'hello-world'});
You can take this function, and use it anywhere. A common use case is to turn
it into a helper function in a template, which you can see in one of our
examples.
Advanced Use Cases
Beyond the simple use case of being able to reference a route by name, and
not having to hard code the URL into your application, there are more advanced
uses of Express Map.
Creating a single-page application today, using a framework like Backbone or
the YUI App Framework, often comes with a few limitations. Rendering HTML on
the server is much faster than sending JSON to the client, and rendering it
there, which is commonly done in today's single page apps.
There can be a noticeable performance problem when rendering data on slower
devices, such as tablets and smartphones, as well as a loss in SEO, since search
engines will have a tougher time crawling your site. What we want to do is
server-side rendering on the first page load, and then client-side rendering
afterwards for the smooth UI with no page refreshes.
What Express Map can help you solve is to share routes between the browser and
the server, so you don't need to write these routes twice, since they're the
same.
We've provided examples for both Backbone and the YUI App Framework, so you
can see how a simple application that does this might work:
- [Backbone Shared Routes Example][]
- [YUI App Framework Shared Routes Example][]
License
This software is free to use under the Yahoo! Inc. BSD license.
See the LICENSE file for license text and copyright information.
Contribute
See the CONTRIBUTING file for information on contributing back to Express
Map.