-
Notifications
You must be signed in to change notification settings - Fork 15
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
- Loading branch information
0 parents
commit f528368
Showing
5 changed files
with
494 additions
and
0 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,2 @@ | ||
**DS_Store | ||
node_modules/ |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,117 @@ | ||
# koa-resource-router | ||
|
||
[](http://travis-ci.org/alexmingoia/koa-resource-router) | ||
[](http://david-dm.org/alexmingoia/koa-resource-router) | ||
[](http://badge.fury.io/js/koa-resource-router) | ||
|
||
RESTful resource routing for [koa](https://github.com/koajs/koa). | ||
|
||
* Rails-like REST resource routing. | ||
* Use multiple middleware for resource actions. | ||
* Responds to `OPTIONS` requests with allowed methods. | ||
* Returns `405 Method Not Allowed` when applicable. | ||
|
||
## Installation | ||
|
||
Install using [npm](https://npmjs.org): | ||
|
||
```sh | ||
npm install koa-resource-router | ||
``` | ||
|
||
## API | ||
|
||
### new Resource(path, actions, options) | ||
|
||
```javascript | ||
var app = require('koa')() | ||
|
||
var users = new Resource('users', { | ||
// GET /users | ||
index: function *(next) { | ||
}, | ||
// GET /users/new | ||
new: function *(next) { | ||
}, | ||
// POST /users | ||
create: function *(next) { | ||
}, | ||
// GET /users/:id | ||
show: function *(next) { | ||
}, | ||
// GET /users/:id/edit | ||
edit: function *(next) { | ||
}, | ||
// PUT /users/:id | ||
update: function *(next) { | ||
}, | ||
// DELETE /users/:id | ||
destroy: function *(next) { | ||
} | ||
}); | ||
|
||
app.use(users.middleware()); | ||
``` | ||
|
||
### Action mapping | ||
|
||
Actions are then mapped accordingly: | ||
|
||
```javascript | ||
GET /users -> index | ||
GET /users/new -> new | ||
POST /users -> create | ||
GET /users/:user -> show | ||
GET /users/:user/edit -> edit | ||
PUT /users/:user -> update | ||
DELETE /users/:user -> destroy | ||
``` | ||
|
||
### Overriding action mapping | ||
|
||
```javascript | ||
var users = new Resource('users', actions, { | ||
methods: { | ||
update: 'PATCH' | ||
} | ||
}); | ||
``` | ||
|
||
### Top-level resource | ||
|
||
Omit the resource name to specify a top-level resource: | ||
|
||
```javascript | ||
var root = new Resource(require('./frontpage')); | ||
``` | ||
|
||
Top-level controller actions are mapped as follows: | ||
|
||
```javascript | ||
GET / -> index | ||
GET /new -> new | ||
POST / -> create | ||
GET /:id -> show | ||
GET /:id/edit -> edit | ||
PUT /:id -> update | ||
DELETE /:id -> destroy | ||
``` | ||
|
||
### Nesting | ||
|
||
Resources can be nested using `resource.add()`: | ||
|
||
```javascript | ||
var forums = new Resource('forums', require('./forum')); | ||
var threads = new Resource('threads', require('./threads')); | ||
|
||
forums.add(threads); | ||
``` | ||
|
||
### Multiple middleware | ||
|
||
```javascript | ||
var users = new Resource('users', authorize, actions); | ||
``` | ||
|
||
## MIT Licensed |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,183 @@ | ||
/** | ||
* Dependencies | ||
*/ | ||
|
||
var compose = require('koa-compose') | ||
, defaults = require('defaults') | ||
, lingo = require('lingo') | ||
, pathToRegExp = require('path-to-regexp'); | ||
|
||
/** | ||
* Expose `Resource` | ||
*/ | ||
|
||
module.exports = Resource; | ||
|
||
/** | ||
* Initialize a new Resource using given `name` and `actions`. | ||
* | ||
* `options` | ||
* - methods override map of action names to http method | ||
* | ||
* @param {String} name | ||
* @param {Function} actions | ||
* @param {Object} options | ||
* @return {Resource} | ||
* @api private | ||
*/ | ||
|
||
function Resource(name, actions, options) { | ||
if (!(this instanceof Resource)) { | ||
return new Resource(name, actions, options); | ||
} | ||
|
||
if (typeof name === 'object') { | ||
actions = name; | ||
name = null; | ||
} | ||
|
||
this.options = { | ||
methods: defaults((options || {}).methods || {}, { | ||
'options': 'OPTIONS', | ||
'new': 'GET', | ||
'create': 'POST', | ||
'edit': 'GET', | ||
'update': 'PUT', | ||
'index': 'GET', | ||
'list': 'GET', | ||
'read': 'GET', | ||
'show': 'GET', | ||
'destroy': 'DELETE', | ||
'remove': 'DELETE' | ||
}) | ||
}; | ||
|
||
this.name = name; | ||
this.id = name ? lingo.en.singularize(name) : 'id'; | ||
this.base = this.name ? '/' + this.name : '/'; | ||
this.actions = actions; | ||
this.routes = []; | ||
this.resources = []; | ||
|
||
// create route definition (used for routing) for each resource action | ||
Object.keys(actions).forEach(function(name) { | ||
var url = this.base; | ||
var urlTrailing = this.base; | ||
|
||
if (url[url.length-1] != '/') { | ||
urlTrailing = url + '/'; | ||
} | ||
|
||
if (name == 'new') { | ||
url = urlTrailing + ':' + this.id; | ||
} | ||
else if (name == 'edit') { | ||
url = urlTrailing + ':' + this.id + '/edit'; | ||
} | ||
else if (name.match(/(show|read|update|remove|destroy)/)) { | ||
url = urlTrailing + ':' + this.id; | ||
} | ||
|
||
var action = actions[name]; | ||
if (action instanceof Array) { | ||
action = compose(actions[name]); | ||
} | ||
|
||
var params = []; | ||
|
||
this.routes.push({ | ||
method: this.options.methods[name].toUpperCase(), | ||
url: url, | ||
regexp: pathToRegExp(url, params), | ||
params: params, | ||
action: action | ||
}); | ||
}, this); | ||
}; | ||
|
||
Resource.prototype.middleware = function() { | ||
var resource = this; | ||
|
||
return function *(next) { | ||
var matched; | ||
|
||
this.params = []; | ||
|
||
if (matched = resource.match(this.url, this.params)) { | ||
var allowedMethods = []; | ||
|
||
for (var len = matched.length, i=0; i<len; i++) { | ||
var route = matched[i]; | ||
|
||
if (this.method == route.method) { | ||
return yield route.action.call(this, next); | ||
} | ||
else { | ||
if (!~allowedMethods.indexOf(route.method)) { | ||
allowedMethods.push(route.method); | ||
} | ||
|
||
} | ||
} | ||
|
||
this.status = (this.method == 'OPTIONS' ? 204 : 405); | ||
this.set('Allow', Object.keys(methodsAllowed).join(", ")); | ||
} | ||
|
||
return yield next; | ||
}; | ||
}; | ||
|
||
Resource.prototype.match = function(url, params) { | ||
var matched = []; | ||
|
||
for (var len = this.routes.length, i=0; i<len; i++) { | ||
var route = this.routes[i]; | ||
|
||
if (route.regexp.test(url)) { | ||
var captures = url.match(route.regexp); | ||
if (captures && captures.length) { | ||
captures = captures.slice(1); | ||
} | ||
|
||
if (params && route.params.length) { | ||
for (var l = captures.length, n=0; n<l; n++) { | ||
if (route.params[n]) { | ||
params[route.params[n].name] = captures[n]; | ||
} | ||
} | ||
} | ||
|
||
matched.push(route); | ||
} | ||
} | ||
|
||
return matched.length ? matched : false; | ||
}; | ||
|
||
/** | ||
* Nest given `resource`. | ||
* | ||
* @param {Resource} resource | ||
* @return {Resource} | ||
* @api public | ||
*/ | ||
|
||
Resource.prototype.add = function(resource) { | ||
var base = this.base[this.base.length-1] == '/' ? this.base : this.base + '/'; | ||
this.resources.push(resource); | ||
|
||
// Re-define base path for nested resource | ||
resource.base = resource.name ? '/' + resource.name : '/'; | ||
resource.base = base + ':' + this.id + resource.base; | ||
|
||
// Re-define route paths for nested resource | ||
for (var len = resource.routes.length, i=0; i<len; i++) { | ||
var route = resource.routes[i]; | ||
route.url = base + ':' + this.id + route.url; | ||
route.params = []; | ||
route.regexp = pathToRegExp(route.url, route.params); | ||
} | ||
|
||
return this; | ||
}; |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,38 @@ | ||
{ | ||
"name": "koa-resource-router", | ||
"description": "RESTful resource routing for koa and koa-router.", | ||
"repository": { | ||
"type": "git", | ||
"url": "https://github.com/alexmingoia/koa-resource.git" | ||
}, | ||
"author": "Alex Mingoia <[email protected]>", | ||
"version": "0.1.0", | ||
"keywords": [ | ||
"rest", | ||
"resource", | ||
"koa", | ||
"router", | ||
"middleware" | ||
], | ||
"main": "./lib/resource.js", | ||
"dependencies": { | ||
"debug": "~0.7.4", | ||
"lingo": "0.0.5", | ||
"path-to-regexp": "0.0.2", | ||
"defaults": "~1.0.0", | ||
"koa-compose": "~2.2.0" | ||
}, | ||
"devDependencies": { | ||
"koa": "0.3.0", | ||
"mocha": "1.12.0", | ||
"should": "1.2.2", | ||
"supertest": "0.7.1" | ||
}, | ||
"scripts": { | ||
"test": "NODE_ENV=test node_modules/mocha/bin/mocha --harmony-generators --reporter spec" | ||
}, | ||
"engines": { | ||
"node": "> 0.11.4" | ||
}, | ||
"license": "MIT" | ||
} |
Oops, something went wrong.