express-map

Exposes server route configuration with the client side via JavaScript

npm install express-map
600 downloads in the last day
2 884 downloads in the last week
3 969 downloads in the last month

Express Map

Build Status Dependency Status npm Version

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:

// Map our paths using Express Map
app.map('/blog/', 'blog');
app.map('/blog/:post', 'blog-post');

// Annotate our paths using Express Annotations
app.annotate('/blog/', {section: 'blog'});
app.annotate('/blog/:post', {section: 'blog'});


// Set up our actual routes
app.get('/blog/', function (req, res) {
    // Render the template for '/blog/' here
});

app.get('/blog/:post', function (req, res) {
    // Render the template for '/blog/:post' here
});

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'});

// routeMap => Will contain only the `blog-post` route

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();

/*
    paramMap => {
        'user': function (req, res, next, id) {
            User.find...
        },
        'post': function (req, res, next, id) {
            Post.find...
        }
    }
*/

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'});
// => '/blog/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.

npm loves you