express-initializers

An Express App initializer pattern to tame large apps.

express-initializers

An Express App initializer pattern to tame large apps.

Usually your server.js or app.js is cluttered with a bunch of app.use and app.set middlewares:

var path = require('path'),
    exphbs = require('express3-handlebars'),
    express = require('express'),
    favicon = require('serve-favicon'),
    port = process.env.PORT || 3000,
    app = express();
 
// Set the port for easy access 
app.set('port', port);
 
// Set up favicon serving 
app.use(favicon('favicon.ico'));
 
// Set up the handlebars view engine 
var hbs = exphbs.create({
    layoutsDir: path.join(__dirname, 'views', 'layouts'),
    partialsDir: path.join(__dirname, 'views', 'partials'),
    defaultLayout: path.join(__dirname, 'views', 'layouts', 'layout.stache'),
    extname: '.stache',
    // Specify helpers here 
    helpers: {
        foofunction () { return 'FOO!'; },
        barfunction () { return 'BAR!'; }
    }
});
app.engine('.stache', hbs.engine);
rname, 'views'));
app.set('view engine', '.stache');
 
/* etc. */
 
app.listen(app.get('port'), function () {
    console.log('Now listening on port ' + app.get('port')); 
});

This module aims to let you break each individual middleware configuration into their own file for tidier code. Given an example directory structure like this:

├── app.js
├── initializers
│   ├── favicon.js
│   ├── port.js
│   ├── routes.js
│   └── views.js

Your app setup file would look something like this:

var express  = require('express'),
    initialize = require('express-initializers'),
 
    app = express();
 
// Let the initializers run 
initialize(app)
    .then(function () {
        // Start listening for requests 
        app.listen(app.get('port'), function () {
            console.log('Now listening on port ' + app.get('port'));
        });
    })
    .catch(function (err) {
        console.log('Unable to initialize app: ' + err.message);
        console.log(err.stack);
    });

And each middleware configuration is moved into its own file. From simple examples like port.js:

// initializers/port.js 
module.exports = {
    configurefunction (app) {
        app.set('port', process.env.PORT || 3000);
    }
};

To more complex things like view engines or db initialization:

// initializers/views.js 
var path = require('path'),
    exphbs = require('express3-handlebars');
 
module.exports = {
    name: 'views',
    after: 'static',
 
    configurefunction (app) {
        // Set up the handlebars view engine 
        var hbs = exphbs.create({
            layoutsDir: path.join(__dirname, 'views', 'layouts'),
            partialsDir: path.join(__dirname, 'views', 'partials'),
            defaultLayout: path.join(__dirname, 'views', 'layouts', 'layout.stache'),
            extname: '.stache',
            // Specify helpers here 
            helpers: {
                foofunction () { return 'FOO!'; },
                barfunction () { return 'BAR!'; }
            }
        });
        app.engine('.stache', hbs.engine);
 
        app.set('views', path.join(__dirname, 'views'));
        app.set('view engine', '.stache');
    }
};
// initializers/db.js 
var db = require('../models/db'),
    Promise = require('bluebird');
 
module.exports = {
    configurefunction (app) {
        return new Promise(function (resolvereject) {
            // Start the db connection 
            db.init(function (err) {
                if (err) {
                    return reject(new Error('Failed to initialize database: ' + err.message));
                }
 
                // Sync all the associations 
                db.sync(function (err) {
                    if (err) {
                        return reject(new Error('Failed to sync database: ' + err.message));
                    }
 
                    app.set('db', db.instance);
 
                    resolve();
                });
            });
        });
    }
};

A more thorough implementation can be seen at node-site.

The initializers function returned from require('express-initializers') can accept options as the second parameter, and an optional callback as the third parameter (if you really hate promises).

initialize(app, {
    // Defaults to the 'initializers' directory relative to the calling file 
    directory: path.join(__dirname, 'configurers'),
    // Defaults to '**/*.js' 
    fileMatch: '**/*.coffee'
}, function (err) {
    if (err) {
        throw err;
    }
 
    app.listen(app.get('port'));
});

Each individual initializer must be a module that exports an object of the form:

module.exports = {
    name: 'something',
    after: 'otherthing',
 
    configurefunction (app) {
        app.set('something', 42);
    } 
};

The name property can be unique or shared amongst a group of initializers.

The after property allows you to order your initializers, it signals that this initializer should be ran after another or a group of other initializers.

The configure method can optionally return a promise for asynchronous configuration.

MIT License, Copyright 2014 Jacob Gable