This is a prototype for the low-level api to serialize express routes into the client side.
npm install
node simple.js
node complex.js
helper.js
is a simple node module that holds a set of utility functions to analyze and transpile the routes from an express application.
As a result, those new transpiled routes can be sent to the client side as a json object or by using express-state
in case you need to serialize functions and regular expressions.
simple.js
uses the very low level express api to response with a json object with the serialized routes that contains the annotations for the express routes. With that, you should be able to use that information in any way you want in the client side, for example, using YAF to redefine those routes, and get an application that can move between pages where those pages are defined as express routes. Pretty cool right?complex.js
usesexpress-state
to support more advanced cases where we serialize functions, replicate functions, optimize functions, and expose regular express. The principle is the same, but this time it just spills a javascript blob that contains the state of the app from the server side to the client.
Adding annotations to existing routes instead of adding a fixed mapping in public/js/app.js
:
app.get('/', helper.annotate(routes.index, 'locate'));
app.get('/places/:id/', helper.annotate(routes.places.load, 'handlePlace'), helper.annotate(routes.places.render, 'showGrid'));
app.get('/photos/:id/', helper.annotate(routes.photos.load, 'handlePhoto'), helper.annotate(routes.photos.render, 'showLightbox'));
Alternative, if those functions under routes
object are named correctly, then there is not need to replicate it here. It will be something like this:
app.get('/', helper.annotate(routes.index));
app.get('/places/:id/', helper.annotate(routes.places.load), helper.annotate(routes.places.render));
app.get('/photos/:id/', helper.annotate(routes.photos.load), helper.annotate(routes.photos.render));
Another alternative, is to do the annotations directly when creating the routes
object, in which case, the result will be:
app.get('/', routes.index);
app.get('/places/:id/', routes.places.load, routes.places.render);
app.get('/photos/:id/', routes.photos.load, routes.photos.render);
while routes.photos.load
will look like:
exports.load = helper.annotate(function handlePhoto(req, res, next) {
// ...
});
app.get('/', app.id('index'), app.render);
app.get('/places/:id/', app.id('places'), app.load, app.render);
app.get('/photos/:id/', app.id('photos'), app.load, app.render);
While we can have some sugar to add those simple methods into app
:
app.id = function (id) {
return helper.annotate(function(req, res, next) {
req.id = id;
res.expose(helper.extractAll(req.app.routes), 'routes');
res.expose(id, 'activeRoute');
next();
}, id);
};
app.load = function (req, res, next) {
if (req.id && route[req.id]) {
route[req.id].load(req, res, next);
} else {
next();
}
};
app.render = function () {
var fn = req.id ? route[req.id].render : route.index;
fn(req, res, next);
};
Of course, nothing really force you to use app.load
and app.render
, you could still do the same we have today:
app.get('/', app.id('index'), routes.index);
app.get('/places/:id/', app.id('places'), routes.places.load, routes.places.render);
app.get('/photos/:id/', app.id('photos'), routes.photos.load, routes.photos.render);