Skip to content

Instantly share code, notes, and snippets.

@j-mcnally
Created August 18, 2011 22:29
Show Gist options
  • Star 2 You must be signed in to star a gist
  • Fork 1 You must be signed in to fork a gist
  • Save j-mcnally/1155433 to your computer and use it in GitHub Desktop.
Save j-mcnally/1155433 to your computer and use it in GitHub Desktop.
Express-resources with middleware
This attempts to add middleware back to Express-resource. For now it only supports
applying the middleware to all the possible routes but hopeful someone else can make that
happen, i had this use case so i did this, if i need more specific middleware i will add it,
i cant imagine that it would be too hard to implement.
/*!
* Express - Resource
* Copyright(c) 2010-2011 TJ Holowaychuk <tj@vision-media.ca>
* Copyright(c) 2011 Daniel Gasienica <daniel@gasienica.ch>
* MIT Licensed
*/
/**
* Module dependencies.
*/
var express = require('express')
, join = require('path').join
, lingo = require('lingo')
, en = lingo.en;
/**
* Initialize a new `Resource` with the given `name` and `actions`.
*
* @param {String} name
* @param {Object} actions
* @param {Server} app
* @api private
*/
var Resource = module.exports = function Resource(name, middleware, actions, app) {
this.name = name;
this.app = app;
this.routes = {};
actions = actions || {};
this.base = actions.base || '/';
if ('/' != this.base[this.base.length - 1]) this.base += '/';
this.format = actions.format;
this.id = actions.id || this.defaultId;
this.param = ':' + this.id;
// default actions
for (var key in actions) {
this.mapDefaultAction(key, middleware, actions[key]);
}
// auto-loader
if (actions.load) this.load(actions.load);
};
/**
* Set the auto-load `fn`.
*
* @param {Function} fn
* @return {Resource} for chaining
* @api public
*/
Resource.prototype.load = function(fn){
var self = this
, id = this.id;
this.loadFunction = fn;
this.app.param(this.id, function(req, res, next){
fn(req.params[id], function(err, obj){
if (err) return next(err);
// TODO: ideally we should next() passed the
// route handler
if (null == obj) return res.send(404);
req[id] = obj;
next();
});
});
return this;
};
/**
* Retun this resource's default id string.
*
* @return {String}
* @api private
*/
Resource.prototype.__defineGetter__('defaultId', function(){
return this.name
? en.singularize(this.name)
: 'id';
});
/**
* Map http `method` and optional `path` to `fn`.
*
* @param {String} method
* @param {String|Function|Object} path
* @param {Function} fn
* @return {Resource} for chaining
* @api public
*/
Resource.prototype.map = function(method, path, mwfnmap){
var middleware = mwfnmap.middleware;
var fn = mwfnmap.fn;
var self = this
, orig = path;
if (method instanceof Resource) return this.add(method);
if ('function' == typeof path) fn = path, path = '';
if ('object' == typeof path) fn = path, path = '';
if ('/' == path[0]) path = path.substr(1);
else path = join(this.param, path);
method = method.toLowerCase();
// setup route pathname
var route = this.base + (this.name || '');
if (this.name && path) route += '/';
route += path;
route += '.:format?';
// register the route so we may later remove it
(this.routes[method] = this.routes[method] || {})[route] = {
method: method
, path: route
, orig: orig
, fn: fn
};
// apply the route
this.app[method](route, middleware, function(req, res, next){
//req.format = req.params.format || self.format;
if (req.format) res.contentType(req.format);
if ('object' == typeof fn) {
if (req.format && fn[req.format]) {
fn[req.format](req, res, next);
} else if (fn.default) {
fn.default(req, res, next);
} else {
res.send(406);
}
} else {
fn(req, res, next);
}
});
return this;
};
/**
* Nest the given `resource`.
*
* @param {Resource} resource
* @return {Resource} for chaining
* @see Resource#map()
* @api public
*/
Resource.prototype.add = function(resource){
var app = this.app
, routes
, route;
// relative base
resource.base = this.base + this.name + '/' + this.param + '/';
// re-define previous actions
for (var method in resource.routes) {
routes = resource.routes[method];
for (var key in routes) {
route = routes[key];
delete routes[key];
app[method](key).remove();
resource.map(route.method, route.orig, route.fn);
}
}
return this;
};
/**
* Map the given action `name` with a callback `fn()`.
*
* @param {String} key
* @param {Function} fn
* @api private
*/
Resource.prototype.mapDefaultAction = function(key, middleware, fn){
var sneakymap = {};
sneakymap.middleware = middleware;
sneakymap.fn = fn;
switch (key) {
case 'index':
this.get('/', sneakymap);
break;
case 'new':
this.get('/new', sneakymap);
break;
case 'create':
this.post('/', sneakymap);
break;
case 'show':
this.get(sneakymap);
break;
case 'edit':
this.get('edit', sneakymap);
break;
case 'update':
this.put(middleware, sneakymap);
break;
case 'destroy':
this.del(middleware, sneakymap);
break;
}
};
/**
* Setup http verb methods.
*/
express.router.methods.concat(['del', 'all']).forEach(function(method){
Resource.prototype[method] = function(path, fn){
if ('function' == typeof path
|| 'object' == typeof path) fn = path, path = '';
this.map(method, path, fn);
return this;
}
});
/**
* Define a resource with the given `name` and `actions`.
*
* @param {String|Object} name or actions
* @param {Object} actions
* @return {Resource}
* @api public
*/
express.HTTPServer.prototype.resource =
express.HTTPSServer.prototype.resource = function(name, middleware, actions, opts){
if (!(middleware instanceof Array)) {
actions = middleware;
opts = actions;
middleware = [];
}
var options = actions || {};
if ('object' == typeof name) actions = name, name = null;
if (options.id) actions.id = options.id;
this.resources = this.resources || {};
if (!actions) return this.resources[name] || new Resource(name, null, this);
for (var key in opts) options[key] = opts[key];
var res = this.resources[name] = new Resource(name, middleware, actions, this);
return res;
};
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment