Skip to content

overlookmotel/expressor

Folders and files

NameName
Last commit message
Last commit date

Latest commit

 

History

45 Commits
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 

Repository files navigation

expressor.js

Easy definition of express routes from a folder structure

Current status

NPM version Build Status Dependency Status Dev dependency Status Coverage Status

API is stable. There are tests covering all features and options.

v2.0.0 contains breaking changes. Please see changelog

What is it for?

Express is a brilliant and simple web framework for node. But most of the time having to define controllers and then manually link them to routes is unnecessary work.

This module allows routes to be defined by placing controllers in files in a folder structure, and then creates express routes for them automatically, based on the folder structure.

Installation

npm install expressor

Basic usage

expressor( app, [path], [options] )

var express = require('express');
var expressor = require('expressor');
var path = require('path');

var app = express();
expressor(app, path.join(__dirname, 'controllers'));

var server = app.listen(3000, function () {
    console.log('Server started');
};

If path is omitted, process.cwd() + 'controllers' will be used. This isn't foolproof, so best to specify the path manually.

With options

expressor(app, '/path/to/controllers', { /* options */ });

or:

expressor(app, {
    path: '/path/to/controllers',
    /* options */
});

Controller definitions

If you want to define the following routes:

/
/help
/users
/users/new
/users/:id
/users/:id/edit
/users/:id/delete

create the following file structure:

controllers/index.js
controllers/help/index.js
controllers/users/index.js
controllers/users/new.js
controllers/users/view.js
controllers/users/edit.js
controllers/users/delete.js

Controller definitions would be as follows:

controllers/index.js

// creates routing '/'
module.exports = {
    get: function (req, res, next) {
        res.send('Welcome to my website');
    }
};

controllers/help/index.js

// creates routing '/help'
module.exports = {
    get: function (req, res, next) {
        res.send('Help page');
    }
};

controllers/users/index.js

// creates routing '/users'
module.exports = {
    get: function (req, res, next) {
        // code to print list of users
    }
};

controllers/users/new.js

// creates routing '/users/new'
module.exports = {
    get: function (req, res, next) {
        // code to print form for new user
    },
    post: function (req, res, next) {
        // code to process form submission
    }
};

controllers/users/view.js

// creates routing '/users/:id'
module.exports = {
    param: 'id',
    pathPart: null, // routes to /users/:id rather than /users/:id/view
    get: function (req, res, next) {
        // code to print details of user
    }
};

controllers/users/edit.js

// creates routing '/users/:id/edit'
module.exports = {
    parentAction: 'view',
    get: function (req, res, next) {
        // code to print form for editing user
    },
    post: function (req, res, next) {
        // code to process form submission
    }
};

controllers/users/delete.js

// creates routing '/users/:id/delete'
module.exports = {
    parentAction: 'view',
    get: function (req, res, next) {
        // code to print confirmation form for deleting user
    },
    post: function (req, res, next) {
        // code to process form submission
    }
};

Routes & actions

Expressor has the concepts of "routes", "actions" and "methods".

  • A route is defined by a folder in the folder structure. e.g. controllers/users/ folder is the users route
  • An action is the controller, defined by a file in the folder structure. e.g. controllers/users/edit.js is the edit action on the users route.
  • A method is defined by a key on the action object e.g. get or post

Route definitions

In addition to defining actions in files, you can also define attributes of the route (i.e. a group of controllers) by placing a file _index.js in the route folder.

// controllers/users/_index.js
module.exports = {
    pathPart: 'accounts'
};

Actions can be defined in this file rather than one controller per file if preferred:

// controllers/users/_index.js
module.exports = {
    actions: {
        index: {
            get: function (req, res, next) { /* ... */ }
        },
        new: { /* ... */ },
        view: { /* ... */ },
        edit: { /* ... */ },
        delete: { /* ... */ }
    }
};

Routing paths

By default routing paths are created as follows:

  • Take the path of the parent action (or parent route's index action for indexes)
  • Add the route name (e.g. users)
  • Add the action's params e.g. param: 'id' adds :id to the path
  • Add the action name (e.g. edit)

Overriding/customizing the path

The path can be customized in various ways.

Define in action definition
// controllers/users/index.js
module.exports = {
    path: '/accounts',
    /* rest of action definition */
};
Change pathPart in route definition

pathPath by default inherits the route's name (the folder name) and is used in constructing the path.

// controllers/users/_index.js
module.exports = {
    pathPart: 'accounts'
};

This achieves the same as the above examples.

Change pathPart in action definition

pathPath by default inherits the action's name (the file name) and is used in constructing the path.

// controllers/users/view.js
module.exports = {
    pathPart: null
};
Params

To create a route /users/:userId/:profileId:

// controllers/users/view.js
module.exports = {
    param: ['userId', 'profileId']
};
parentAction

Sit this action on top of another action.

If parentAction begins with '../', it sits on top of the named action in the parent route.

e.g. for /users/:userId/permissions

// controllers/users/permissions/index.js
module.exports = {
    parentAction: '../view',
    get: function(req, res) { /* etc etc */ }
};

Setting parentAction in the above example sits the route on top of the users/view action rather than the default users/index. i.e. makes the route path start /users/:userId/ rather than /users/.

Path tricks

To make github-style routes /:organisation/:repo:

  • Create a route folder controllers/organisations
  • Set pathPart: null in organisations route controller (controllers/organisations/_index.js)
  • Set pathPath: null in organisations view action (controllers/organisations/view.js)
  • Set param: 'organisation' in organisations view action (controllers/organisations/view.js)
  • Create a route folder controllers/organisations/repos
  • Set pathPart: null in repos route controller (controllers/organisations/repos/_index.js)
  • Set parentAction: '../view' in repos index action (controllers/organisations/repos/index.js)
  • Set pathPath: null in repos view action (controllers/organisations/repos/view.js)
  • Set param: 'repo' in repos view action (controllers/organisations/repos/view.js)

The file controllers/organisations/repos/view.js will then map to '/:organisation/:repo'.

Advanced usage

Options

Options should be passed to expressor(app, options) or expressor(app, path, options).

methods

Set what methods can be used on actions. Defaults to ['get', 'post'].

expressor(app, path, { methods: [ 'get', 'post', 'put', 'delete' ] });

endSlash

Controls whether to add a trailing / on the end of routes with empty action pathPart. Defaults to false.

Routings with endSlash = false:

/users
/users/new
/users/:id
/users/:id/edit
/users/:id/delete

With endSlash = true:

/users/
/users/new
/users/:id/
/users/:id/edit
/users/:id/delete

(NB only /users/ and /users/:id/ are affected)

indexAction

Set what action is the "index" action i.e. the default action of a route. Defaults to 'index'.

routeFile

Set name of files containing route definitions. Defaults to '_index.js'.

paramAttribute

Changes attribute of actions that contains param names. Defaults to 'param'.

logger

If a function is provided as options.logger, it is called for each route which is attached to express with a message in format 'Attached route: [method] [path]'.

expressor(app, path, {logger: console.log});

wrapper

A function that wraps all controller method functions. Wrapper is called with (fn, method, action, app) and should return a function which will be set as the controller method function in place of the original.

e.g. If you want to write your controller functions to return promises rather than use callbacks:

expressor(app, path, {
    wrapper: function(fn, method, action, app) {
        return function(req, res, next) {
            fn(req, res).then(function() {
                console.log('Request handled: ' + req.url);
            }).catch(function(err) {
                next(err);
            });
        };
    }
});

Then an action might be as defined as follows:

// controllers/users/index.js
module.exports = {
    get: function(req, res) {
        return User.findAll().then(function(users) {
            res.json(users);
        });
    }
};

load

A set of options passed to require-folder-tree which loads the routing files.

Defaults to the following options:

{
    filterFiles: /^([^\._].*)\.js$/,
    filterFolders: /^([^\._].*)$/
}

i.e. ignores files and folders with file names starting with '.' or '_'.

You could, for example, change these options to write routing files in coffeescript.

Hooks

To allow customization, hooks can be set to run on the tree of routes, either before paths of each action are defined or after.

Hooks are defined in options.hooks. Hook functions are:

treeBeforePath / treeAfterPath

Called with params (tree, app) where tree is the complete tree of routes. app is the express app.

routeBeforePath / routeAfterPath

Called on each route in the entire routing tree, with params (route, app).

actionBeforePath / actionAfterPath

Called on each action in the entire routing tree, with params (action, app).

Example

This example sets the param field of each action where param is defined as true to '[route name]Id', so that routings are defined like /users/:userId/permissions/:permissionId

expressor(app, path, {
    hooks: {
        actionBeforePath: function(action, app) {
            if (action.param === true) {
                action.param = inflection.singularize(action.route.name) + 'Id';
            }
        }
    }
});

NB inflection is a package for converting words between singular and plural.

Tests

Use npm test to run the tests. Use npm run cover to check coverage.

Changelog

See changelog.md

Issues

If you discover a bug, please raise an issue on Github. https://github.com/overlookmotel/expressor/issues

Contribution

Pull requests are very welcome. Please:

  • ensure all tests pass before submitting PR
  • add an entry to changelog
  • add tests for new features
  • document new functionality/API additions in README

About

Easy definition of express routes from a folder structure

Resources

License

Stars

Watchers

Forks

Packages

No packages published