express-enrouten
Advanced tools
Comparing version 0.3.0 to 1.0.0
210
index.js
@@ -20,67 +20,66 @@ /*───────────────────────────────────────────────────────────────────────────*\ | ||
var fs = require('fs'), | ||
path = require('path'), | ||
assert = require('assert'), | ||
express = require('express'); | ||
var path = require('path'); | ||
var caller = require('caller'); | ||
var express = require('express'); | ||
var reverend = require('reverend'); | ||
var debug = require('debuglog')('enrouten'); | ||
var index = require('./lib/index'); | ||
var routes = require('./lib/routes'); | ||
var registry = require('./lib/registry'); | ||
var directory = require('./lib/directory'); | ||
/** | ||
* Resolve and recursively scan the provided directory | ||
* @param dir the directory to scan. | ||
* @returns {Array} absolute file paths that are able to be loaded by Node code. | ||
* Creates the onmount handler used to process teh middelwarez | ||
* @param app the sacrificial express app to use. | ||
* @param options the configuration settings to use when scanning | ||
* @returns {Function} | ||
*/ | ||
function loaddir(dir) { | ||
return scan(resolve(dir)); | ||
} | ||
function mount(app, options) { | ||
return function onmount(parent) { | ||
var router; | ||
/** | ||
* Returns true if `require` is able to load the provided file | ||
* or false if not. | ||
* http://nodejs.org/api/modules.html#modules_file_modules | ||
* @param file the file for which to determine module-ness. | ||
* @returns {boolean} | ||
*/ | ||
function isFileModule(file) { | ||
var ext = path.extname(file); | ||
// Remove sacrificial express app and keep a | ||
// copy of the currently registered items. | ||
/// XXX: caveat emptor, private member | ||
parent._router.stack.pop(); | ||
router = registry(app.mountpath); | ||
try { | ||
// remove the file extension and use require.resolve to resolve known | ||
// file types eg. CoffeeScript. Will throw if not found/loadable by node. | ||
file = ext ? file.slice(0, -ext.length) : file; | ||
require.resolve(file); | ||
return true; | ||
} catch (err) { | ||
return false; | ||
} | ||
} | ||
// Process the configuration, adding to the stack | ||
if (typeof options.index === 'string') { | ||
options.index = resolve(options.basedir, options.index); | ||
index(router, options.index); | ||
} | ||
if (typeof options.directory === 'string') { | ||
options.directory = resolve(options.basedir, options.directory); | ||
directory(router, options.directory); | ||
} | ||
/** | ||
* Recursively (synchronously) scans the provided root directory, locating files which | ||
* are able to be loaded by node. | ||
* @param dir the root dir to begin scanning | ||
* @param controllers an array containing the absolute file paths of all found controllers | ||
* @returns {Array} the controllers array. | ||
*/ | ||
function scan(dir, controllers) { | ||
var stats; | ||
if (typeof options.routes === 'object') { | ||
routes(router, options.routes); | ||
} | ||
controllers = controllers || []; | ||
// Setup app locals for use in handlers. | ||
parent.locals.enrouten = { | ||
stats = fs.statSync(dir); | ||
routes: router.routes, | ||
if (stats.isDirectory()) { | ||
// recursively scan child files | ||
fs.readdirSync(dir).forEach(function (child) { | ||
scan(path.join(dir, child), controllers); | ||
}); | ||
} | ||
path: function path(name, data) { | ||
var route; | ||
route = this.routes[name]; | ||
if (typeof route === 'string') { | ||
return reverend(route, data || {}); | ||
} | ||
return undefined; | ||
} | ||
if (stats.isFile()) { | ||
// add if valid | ||
isFileModule(dir) && controllers.push(dir); | ||
} | ||
}; | ||
return controllers; | ||
debug('mounting routes at', app.mountpath); | ||
debug(router.routes); | ||
parent.use(app.mountpath, router._router); | ||
}; | ||
} | ||
@@ -90,87 +89,52 @@ | ||
/** | ||
* Helper for resolving a relative file path or array | ||
* of path segments. | ||
* @param file file path or array of path segments. | ||
* @returns {String} the resolved file path | ||
* Resolves the provide basedir and file, returning | ||
* and absolute file path. | ||
* @param basedir the base directory to use in path resolution | ||
* @param file the absolute or relative file path to resolve. | ||
* @returns {String} the resolved absolute file path. | ||
*/ | ||
function resolve(file) { | ||
if (!file) { | ||
return undefined; | ||
function resolve(basedir, file) { | ||
if (path.resolve(file) === file) { | ||
// absolute path | ||
return file; | ||
} | ||
if (Array.isArray(file)) { | ||
file = path.join.apply(undefined, file); | ||
} | ||
file = path.resolve(file); | ||
return file; | ||
return path.join(basedir, file); | ||
} | ||
module.exports = function (settings) { | ||
/** | ||
* The main entry point for this module. Creates middleware | ||
* to be mounted to a parent application. | ||
* @param options the configuration settings for this middleware instance | ||
* @returns {Function} express middleware | ||
*/ | ||
function enrouten(options) { | ||
var app; | ||
function initialize(app, settings) { | ||
// If index specified, use it. | ||
if (settings.index) { | ||
require(resolve(settings.index))(app); | ||
} | ||
options = options || {}; | ||
options.basedir = options.basedir || path.dirname(caller()); | ||
// If directory specified, scan | ||
if (settings.directory) { | ||
loaddir(settings.directory).forEach(function (file) { | ||
var controller = require(file); | ||
if (typeof controller === 'function' && controller.length === 1) { | ||
controller(app); | ||
} | ||
}); | ||
} | ||
app = express(); | ||
app.once('mount', mount(app, options)); | ||
// Finally, try specified routes | ||
if (Array.isArray(settings.routes)) { | ||
settings.routes.forEach(function (def) { | ||
var method; | ||
return app; | ||
} | ||
assert.ok(def.path, 'path is required'); | ||
assert.ok(typeof def.handler === 'function', 'handler is required'); | ||
method = (def.method || 'get').toLowerCase(); | ||
app[method](def.path, def.handler); | ||
}); | ||
} | ||
/** | ||
* Create a URL from a named route and data. | ||
* @param app the express app for which to generate the named route | ||
* @param name the name of the route to generate | ||
* @param data the object containing keys and values for the named replacements. | ||
* @returns {String} the generated URL or undefined if no named route exists. | ||
*/ | ||
enrouten.path = function path(app, name, data) { | ||
var locals = app.locals; | ||
if (locals.enrouten && typeof locals.enrouten.path === 'function') { | ||
return locals.enrouten.path(name, data); | ||
} | ||
return undefined; | ||
}; | ||
function mount(settings) { | ||
return function onmount(parent) { | ||
// Remove sacrificial express app | ||
parent.stack.pop(); | ||
// Process the configuration. | ||
initialize(parent, settings); | ||
// Reorganize stack to place router in correct place | ||
// This could get out of whack if someone registers | ||
// directly against express prior to calling enrouten. | ||
// This is done *after* scanning so any middleware registered | ||
// during scanning is correctly put before the router. | ||
parent.stack.some(function (middleware, idx, stack) { | ||
if (middleware.handle.name === 'router') { | ||
// If a route was specified when mounting, update the | ||
// router middleware to only respond to that route. | ||
// e.g. app.use('/foo', enrouten()) is akin to app.use('/foo', app.router); | ||
middleware.route = app.route !== '/' ? app.route : ''; | ||
// Reorder stack | ||
stack.splice(idx, 1); | ||
stack.push(middleware); | ||
return true; | ||
} | ||
return false; | ||
}); | ||
}; | ||
} | ||
app = express(); | ||
app.once('mount', mount(settings)); | ||
return app; | ||
}; | ||
module.exports = enrouten; |
{ | ||
"name": "express-enrouten", | ||
"version": "0.3.0", | ||
"version": "1.0.0", | ||
"description": "An express route initialization and configuration module.", | ||
"main": "index.js", | ||
"scripts": { | ||
"test": "grunt test" | ||
"test": "tape test/*.js", | ||
"cover": "istanbul cover tape -- test/*.js", | ||
"lint": "jshint -c .jshintrc index.js lib/*.js" | ||
}, | ||
"repository": "git://github.com/paypal/express-enrouten.git", | ||
"repository": "git://github.com/krakenjs/express-enrouten.git", | ||
"publishConfig": { | ||
@@ -28,10 +30,13 @@ "registry": "https://registry.npmjs.org" | ||
"devDependencies": { | ||
"mocha": "~1.9.0", | ||
"chai": "~1.5.0", | ||
"grunt": "~0.4.1", | ||
"grunt-contrib-jshint": "~0.7.0", | ||
"grunt-mocha-test": "~0.7.0", | ||
"express": "~3.4.6", | ||
"supertest": "~0.8.2" | ||
"express": "~4.0.0", | ||
"supertest": "~0.8.2", | ||
"tape": "~2.10.2", | ||
"istanbul": "~0.2.6", | ||
"jshint": "~2.4.4" | ||
}, | ||
"dependencies": { | ||
"caller": "~0.0.1", | ||
"debuglog": "~1.0.1", | ||
"reverend": "~0.2.0" | ||
} | ||
} |
107
README.md
@@ -6,4 +6,7 @@ express-enrouten | ||
Note: `express-enrouten >=1.0` is only compatible with `express >=4.0`. | ||
For `express 3.x` support, please use `express-enrouten 0.3.x`. | ||
[![Build Status](https://travis-ci.org/paypal/express-enrouten.png)](https://travis-ci.org/paypal/express-enrouten) | ||
[![Build Status](https://travis-ci.org/krakenjs/express-enrouten.png)](https://travis-ci.org/krakenjs/express-enrouten) | ||
[![NPM version](https://badge.fury.io/js/express-enrouten.png)](http://badge.fury.io/js/express-enrouten) | ||
@@ -26,26 +29,60 @@ | ||
```javascript | ||
app.use(enrouten({ | ||
routes: [{ | ||
method: 'GET', | ||
path: '/foo', | ||
handler: function (req, res) { | ||
// ... | ||
} | ||
}] | ||
} | ||
}); | ||
app.use(enrouten({ directory: 'routes' })); | ||
``` | ||
- `directory` (optional) - String or array of path segments. Specify a directory to have enrouten scan all files recursively | ||
to find files that match the controller-spec API. | ||
#### directory | ||
The `directory` configuration option (optional) is the path to a directory. | ||
Specify a directory to have enrouten scan all files recursively to find files | ||
that match the controller-spec API. With this API, the directory structure | ||
dictates the paths at which handlers will be mounted. | ||
```text | ||
controllers | ||
|-user | ||
|-create.js | ||
|-list.js | ||
``` | ||
```javascript | ||
// create.js | ||
module.exports = function (router) { | ||
router.post('/', function (req, res) { | ||
res.send('ok'); | ||
}); | ||
}; | ||
``` | ||
```javascript | ||
app.use(enrouten({ | ||
directory: 'controllers' | ||
}); | ||
})); | ||
``` | ||
Routes are now: | ||
```test | ||
/user/create | ||
/user/list | ||
``` | ||
- `routes` (optional) An array of route definition objects. Each definition must have a `path` and `handler` property and | ||
can have an optional `method` property (`method` defaults to 'GET'). | ||
#### index | ||
The `index` configuration option (optional) is the path to the single file to | ||
load (which acts as the route 'index' of the application). | ||
```javascript | ||
app.use(enrouten({ | ||
index: 'routes/' | ||
})); | ||
``` | ||
```javascript | ||
// index.js | ||
module.exports = function (router) { | ||
router.get('/', index); | ||
router.all(passport.protect).get('/account', account); | ||
// etc... | ||
}; | ||
``` | ||
#### routes | ||
The `routes` configuration option (optional) is an array of route definition objects. | ||
Each definition must have a `path` and `handler` property and can have an optional | ||
`method` property (`method` defaults to 'GET'). | ||
```javascript | ||
@@ -57,21 +94,31 @@ app.use(enrouten({ | ||
] | ||
}); | ||
})); | ||
``` | ||
- `index` (optional, overrides `directory` and disables scanning) - String path or array of path segments indicating | ||
the file to load which acts as the route 'index' of the application. | ||
### Named Routes | ||
For `index` and `directory` configurations there is also support for named routes. | ||
The normal express router that is passed in will always behave as such, but in addition | ||
it can be used to name a route, adding the name and path to `app.locals.enrouten.routes`. | ||
For example: | ||
```javascript | ||
// index.js | ||
module.exports = function (app) { | ||
'use strict'; | ||
app.get('/', index); | ||
app.get('/account', passport.protect, account); | ||
module.exports = function (router) { | ||
// etc... | ||
router({ path: '/user/:id', name: 'user-info' }) | ||
.get(function (req, res) { | ||
res.send('ok'); | ||
}); | ||
}; | ||
``` | ||
### Controller Files | ||
A 'controller' is defined as any `require`-able file which exports a function that accepts a single argument. Any files with an extension of `.js` (or `.coffee` if CoffeeScript is registered) will be loaded and if it exports a function that accepts a single argument then this function will be called. **NOTE: Any file in the directory tree that matches the API will be invoked/initialized with the express application object.** | ||
A 'controller' is defined as any `require`-able file which exports a function | ||
that accepts a single argument. Any files with an extension of `.js` (or `.coffee` | ||
if CoffeeScript is registered) will be loaded and if it exports a function that | ||
accepts a single argument then this function will be called. **NOTE: Any file in | ||
the directory tree that matches the API will be invoked/initialized with the | ||
express router object.** | ||
@@ -81,4 +128,4 @@ ```javascript | ||
// controllers/controller.js | ||
module.exports = function (app) { | ||
app.get('/', function (req, res) { | ||
module.exports = function (router) { | ||
router.get('/', function (req, res) { | ||
// ... | ||
@@ -90,3 +137,3 @@ }); | ||
// Function does not get returned when `require`-ed, use `module.exports` | ||
exports = function (app) { | ||
exports = function (router) { | ||
// ... | ||
@@ -98,3 +145,3 @@ }; | ||
modules.exports = function (config) { | ||
// `config` will be the express application | ||
// `config` will be an express Router | ||
// ... | ||
@@ -101,0 +148,0 @@ }; |
Sorry, the diff of this file is not supported yet
Major refactor
Supply chain riskPackage has recently undergone a major refactor. It may be unstable or indicate significant internal changes. Use caution when updating to versions that include significant changes.
Found 1 instance in 1 package
Dynamic require
Supply chain riskDynamic require can indicate the package is performing dangerous or unsafe dynamic code execution.
Found 1 instance in 1 package
No repository
Supply chain riskPackage does not have a linked source code repository. Without this field, a package will have no reference to the location of the source code use to generate the package.
Found 1 instance in 1 package
Dynamic require
Supply chain riskDynamic require can indicate the package is performing dangerous or unsafe dynamic code execution.
Found 1 instance in 1 package
No repository
Supply chain riskPackage does not have a linked source code repository. Without this field, a package will have no reference to the location of the source code use to generate the package.
Found 1 instance in 1 package
No v1
QualityPackage is not semver >=1. This means it is not stable and does not support ^ ranges.
Found 1 instance in 1 package
197270
5
26
395
1
154
3
1
+ Addedcaller@~0.0.1
+ Addeddebuglog@~1.0.1
+ Addedreverend@~0.2.0
+ Addedcaller@0.0.1(transitive)
+ Addeddebuglog@1.0.1(transitive)
+ Addeddeep-equal@0.1.2(transitive)
+ Addeddefined@0.0.0(transitive)
+ Addedinherits@2.0.4(transitive)
+ Addedjsonify@0.0.1(transitive)
+ Addedpath-to-regexp@0.1.12(transitive)
+ Addedresumer@0.0.0(transitive)
+ Addedreverend@0.2.0(transitive)
+ Addedtape@2.3.3(transitive)
+ Addedthrough@2.3.8(transitive)