Socket
Socket
Sign inDemoInstall

express-resource

Package Overview
Dependencies
Maintainers
0
Versions
9
Alerts
File Explorer

Advanced tools

Socket logo

Install Socket

Detect and block malicious and high-risk dependencies

Install

express-resource - npm Package Compare versions

Comparing version 0.1.0 to 0.2.0

12

History.md
0.2.0 / 2011-04-09
==================
* Added basic content-negotiation support via format extensions
* Added nested resource support
* Added auto-loading support, populating `req.user` etc automatically
* Added another options param to `app.resource()`
* Added `Resource#[http-verb]()` methods to define additional routes
* Added `Resource#map(method, path, fn)`
* Changed; every `Resource` has a `.base`
* Changed; resource id is no longer "id", it's a singular version of the resource name, aka `req.params.user` etc
0.1.0 / 2011-03-27

@@ -3,0 +15,0 @@ ==================

188

index.js

@@ -13,3 +13,5 @@

var express = require('express');
var express = require('express')
, lingo = require('lingo')
, en = lingo.en;

@@ -26,46 +28,177 @@ /**

var Resource = module.exports = function Resource(name, actions, app) {
this.base = '/';
this.name = name;
this.app = app;
this.actions = actions
this.id = actions.id || 'id';
this.routes = {};
actions = actions || {};
this.format = actions.format;
this.id = actions.id || this.defaultId;
this.param = ':' + this.id;
// default actions
for (var key in actions) {
this.defineAction(key, actions[key]);
this.mapDefaultAction(key, actions[key]);
}
// auto-loader
if (actions.load) this.load(actions.load);
};
/**
* Define the given action `name` with a callback `fn()`.
* Set the auto-load `fn`.
*
* @param {String} key
* @param {Function} fn
* @return {Resource} for chaining
* @api public
*/
Resource.prototype.defineAction = function(key, fn){
var app = this.app
, id = this.id
, name = '/' + (this.name || '')
, path = this.name ? name + '/' : '/';
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, fn){
var self = this;
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);
method = method.toLowerCase();
// setup route pathname
var route = this.base + (this.name || '');
route += (this.name && path) ? '/' : '';
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: path
, fn: fn
};
// apply the route
this.app[method](route, 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(415);
}
} 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 router = this.app.router
, 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];
router.remove(key, route.method);
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, fn){
var id = this.param;
switch (key) {
case 'index':
app.get(name, fn);
this.get(fn);
break;
case 'new':
app.get(path + 'new', fn);
this.get('new', fn);
break;
case 'create':
app.post(name, fn);
this.post(fn);
break;
case 'show':
app.get(path + ':' + id, fn);
this.get(id, fn);
break;
case 'edit':
app.get(path + ':' + id + '/edit', fn);
this.get(id + '/edit', fn);
break;
case 'update':
app.put(path + ':' + id, fn);
this.put(id, fn);
break;
case 'destroy':
app.del(path + ':' + id, fn);
this.del(id, fn);
break;

@@ -76,2 +209,15 @@ }

/**
* 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`.

@@ -86,7 +232,11 @@ *

express.HTTPServer.prototype.resource =
express.HTTPSServer.prototype.resource = function(name, actions){
express.HTTPSServer.prototype.resource = function(name, actions, opts){
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, actions, this);
return res;
};

3

package.json
{ "name": "express-resource"
, "description": "Resourceful routing for express"
, "version": "0.1.0"
, "version": "0.2.0"
, "author": "TJ Holowaychuk <tj@vision-media.ca>"

@@ -8,2 +8,3 @@ , "contributors": [

]
, "dependencies": { "lingo": ">= 0.0.4" }
, "keywords": ["express", "rest", "resource"]

@@ -10,0 +11,0 @@ , "main": "index"

@@ -14,3 +14,3 @@

To get started simply `require('express-resource')`, and this module will monkey-patch the `express.Server`, enabling resourceful routing. A "resource" is simply an object, which defines one of more of the supported "actions" listed below:
To get started simply `require('express-resource')`, and this module will monkey-patch Express, enabling resourceful routing by providing the `app.resource()` method. A "resource" is simply an object, which defines one of more of the supported "actions" listed below:

@@ -45,12 +45,4 @@ exports.index = function(req, res){

The _id_ option can be specified to prevent collisions:
The `app.resource()` method returns a new `Resource` object, which can be used to further map pathnames, nest resources, and more.
exports.id = 'uid';
exports.destroy = function(req, res) {
res.send('destroy user ' + req.params.uid);
};
The `app.resource()` method will create and return a new `Resource`:
var express = require('express')

@@ -62,18 +54,17 @@ , Resource = require('express-resource')

Actions are then mapped as follows (by default):
## Default Action Mapping
GET /forums -> index
GET /forums/new -> new
POST /forums -> create
GET /forums/:id -> show
GET /forums/:id/edit -> edit
PUT /forums/:id -> update
DELETE /forums/:id -> destroy
Actions are then mapped as follows (by default), providing `req.params.forum` which contains the substring where ":forum" is shown below:
GET /forums -> index
GET /forums/new -> new
POST /forums -> create
GET /forums/:forum -> show
GET /forums/:forum/edit -> edit
PUT /forums/:forum -> update
DELETE /forums/:forum -> destroy
Specify a top-level resource using the empty string:
## Top-Level Resource
var express = require('express')
, Resource = require('express-resource')
, app = express.createServer();
Specify a top-level resource by omitting the resource name:

@@ -92,5 +83,77 @@ app.resource(require('./forum'));

## Auto-Loading
__NOTE:__ this functionality will surely grow with time, and as data store clients evolve we can provide close integration.
Resources have the concept of "auto-loading" associated data. For example we can pass a "load" property along with our actions, which should invoke the callback function with an error, or the object such as a `User`:
User.load = function(id, fn) {
fn(null, users[id]);
};
app.resource('users', { show: ..., load: User.load });
With the auto-loader defined, the `req.user` object will be available now be available to the actions automatically. We may pass the "load" option as the third param as well, although this is equivalent to above, but allows you to either export ".load" along with your actions, or passing it explicitly:
app.resource('users', require('./user'), { load: User.load });
Finally we can utilize the `Resource#load(fn)` method, which again is functionally equivalent:
var user = app.resource('users', require('./user'));
user.load(User.load);
This functionality works when nesting resources as well, for example suppose we have a forum, which contains threads, our setup may look something like below:
var forums = app.resource('forums', require('resources/forums'), { load: Forum.get });
var threads = app.resources('threads', require('resources/threads'), { load: Thread.get });
forums.add(threads);
Now when we request `GET /forums/5/threads/12` both the `req.forum` object, and `req.thread` will be available to thread's _show_ action.
## Content-Negotiation
Currently express-resource supports basic content-negotiation support utilizing extnames or "formats". This can currently be done two ways, first we may define actions as we normally would, and utilize the `req.format` property, and respond accordingly. The following would respond to `GET /pets.xml`, and `GET /pets.json`.
var pets = ['tobi', 'jane', 'loki'];
exports.index = function(req, res){
switch (req.format) {
case 'json':
res.send(pets);
break;
case 'xml':
res.send('<pets>' + pets.map(function(pet){
return '<pet>' + pet + '</pet>';
}).join('') + '</pets>');
break;
default:
res.send(415);
}
};
The following is equivalent, however we separate the logic into several callbacks, each representing a format.
exports.index = {
json: function(req, res){
res.send(pets);
},
xml: function(req, res){
res.send('<pets>' + pets.map(function(pet){
return '<pet>' + pet + '</pet>';
}).join('') + '</pets>');
}
};
We may also provide a `default` format, invoked when either no extension is given, or one that does not match another method is given:
exports.default = function(req, res){
res.send('Unsupported format "' + req.format + '"', 415);
};
To assign a default format to an existing method, we can provide the `format` option to the resource. With the following definition both `GET /users/5` and `GET /users/5.json` will invoke the `show.json` action, or `show` with `req.format = 'json'`.
app.resource('users', actions, { format: 'json' });
## Running Tests

@@ -97,0 +160,0 @@

Sorry, the diff of this file is not supported yet

Sorry, the diff of this file is not supported yet

SocketSocket SOC 2 Logo

Product

  • Package Alerts
  • Integrations
  • Docs
  • Pricing
  • FAQ
  • Roadmap
  • Changelog

Packages

npm

Stay in touch

Get open source security insights delivered straight into your inbox.


  • Terms
  • Privacy
  • Security

Made with ⚡️ by Socket Inc