Asymmetrik/mean2-starter

View on GitHub
src/server/lib/express.js

Summary

Maintainability
A
2 hrs
Test Coverage
'use strict';

/**
 * Module dependencies.
 */
let path = require('path'),
    config = require(path.resolve('./src/server/config.js')),
    logger = require(path.resolve('./src/server/lib/bunyan.js')).logger,

    bodyParser = require('body-parser'),
    compress = require('compression'),
    cookieParser = require('cookie-parser'),
    express = require('express'),
    handlebars = require('express-handlebars'),
    session = require('express-session'),
    favicon = require('serve-favicon'),
    flash = require('connect-flash'),
    helmet = require('helmet'),
    methodOverride = require('method-override'),
    morgan = require('morgan'),
    passport = require('passport'),

    MongoStore = require('connect-mongo')(session);

/**
 * Initialize local variables
 */
function initLocalVariables(app) {
    // Setting application local variables
    app.locals.title = config.app.title;
    app.locals.description = config.app.description;
    app.locals.keywords = config.app.keywords;

    // Asset files
    app.locals.jsFiles = config.files.client.js;
    app.locals.cssFiles = config.files.client.css;

    // Development
    app.locals.developmentMode = config.mode === 'development';
    if(app.locals.developmentMode) {
        app.locals.webpackDevServer = `${config.app.baseUrlWithoutPort}:${config.devPorts.webpack}`;
    }

    // Live Reload
    app.locals.liveReload = config.liveReload;
    if (config.liveReload) {
        app.locals.liveReloadScript = `${config.app.baseUrlWithoutPort}:${config.liveReload.port}/livereload.js`;
    }

    // Passing the request url to environment locals
    app.use(function (req, res, next) {
        res.locals.host = config.app.baseUrlWithoutPort;
        res.locals.url = config.app.baseUrl + req.originalUrl;
        next();
    });
}

/**
 * Initialize application middleware
 */
function initMiddleware(app) {
    // Showing stack errors
    app.set('showStackError', true);

    // Should be placed before express.static
    app.use(compress({
        filter: function (req, res) {
            if (req.headers['x-no-compression']) {
                // don't compress responses with this request header
                return false;
            }

            // fallback to standard filter function
            return compress.filter(req, res);
        },
        level: 6
    }));

    // Initialize favicon middleware
    app.use(favicon(path.resolve('./src/client/app/img/brand/favicon.ico')));

    // Environment dependent middleware
    if (config.mode === 'development') {
        // Disable views cache
        app.set('view cache', false);
    } else if (config.mode === 'production') {
        app.locals.cache = 'memory';
    }

    // Optionally turn on express logging
    if (config.expressLogging) {
        app.use(morgan('dev'));
    }

    // Request body parsing middleware should be above methodOverride
    app.use(bodyParser.urlencoded({
        extended: true
    }));
    app.use(bodyParser.json());
    app.use(methodOverride());

    // Add the cookie parser and flash middleware
    app.use(cookieParser(config.auth.sessionSecret));
    app.use(flash());

}

/**
 * Configure view engine
 */
function initViewEngine(app) {

    let hbs = handlebars.create({
        extname: '.server.view.html',
        defaultLayout: 'main',
        layoutsDir: path.resolve('./src/server/app/core/views/layouts'),
        partialsDir: path.resolve('./src/server/app/core/views'),
        helpers: {
            block: function(name) {
                let blocks = this._blocks;
                let content = blocks && blocks[name];
                return content ? content.join('\n') : null;
            },
            contentFor: function(name, options) {
                let blocks = this._blocks || (this._blocks = {});
                let block = blocks[name] || (blocks[name] = []);
                block.push(options.fn(this));
            }
        }
    });
    app.engine('.server.view.html', hbs.engine);

    // Set views path and view engine
    app.set('view engine', '.server.view.html');
    app.set('views', path.resolve('./src/server/app/core/views'));
}

/**
 * Configure Express session
 */
function initSession(app, db) {
    // Express MongoDB session storage
    app.use(session({
        saveUninitialized: true,
        resave: true,
        secret: config.auth.sessionSecret,
        cookie: config.auth.sessionCookie,
        store: new MongoStore({
            db: db.connection.db,
            collection: config.auth.sessionCollection
        })
    }));
}

/**
 * Configure passport
 */
function initPassport(app) {
    app.use(passport.initialize());
    app.use(passport.session());

    require(path.resolve('./src/server/lib/passport.js')).init();
}

/**
 * Invoke modules server configuration
 */
function initModulesConfiguration(app, db) {
    config.files.server.configs.forEach(function (configPath) {
        require(path.resolve(configPath))(app, db);
    });
}

/**
 * Configure Helmet headers configuration
 */
function initHelmetHeaders(app) {
    // Use helmet to secure Express headers
    app.use(helmet.frameguard());
    app.use(helmet.xssFilter());
    app.use(helmet.noSniff());
    app.use(helmet.ieNoOpen());
    app.disable('x-powered-by');
}

/**
 * Configure the modules static routes
 */
function initModulesClientRoutes(app) {
    /*
     * Exposing the public directory
     */

    // Expose dev app files (w/ no caching) Order matters here, subdirs first
    app.use('/dev', express.static(path.resolve('./public/dev'), { maxAge: 0 }));

    // Expose the bundled production app files (with caching)
    app.use('/', express.static(path.resolve('./public'), { maxAge: 31536000000 }));

    // Expose the source application resources that aren't compiled/bundled
    // These are not cached for the time being since they are served from the subdirs directly and there is no
    // way to easily cache bust them
    config.folders.client.forEach(function (staticPath) {
        app.use(staticPath, express.static(path.resolve('./' + staticPath), { maxAge: 0 }));
        app.use(staticPath.replace('/client', ''), express.static(path.resolve('./' + staticPath), { maxAge: 0 }));
    });
}

/**
 * Configure the modules ACL policies
 */
function initModulesServerPolicies(app) {
    // Globbing policy files
    config.files.server.policies.forEach(function (policyPath) {
        require(path.resolve(policyPath)).invokeRolesPolicies();
    });
}

/**
 * Configure the modules server routes
 */
function initModulesServerRoutes(app) {
    // Init the global route prefix
    let router = express.Router();

    // Globbing routing files to be behind a common path
    config.files.server.routes.forEach(function (routePath) {
        router.use(require(path.posix.resolve(routePath)));
    });

    app.use(router);
}

/**
 * Configure the modules sockets by simply including the files.
 * Do not instantiate the modules.
 */
function initModulesServerSockets(app) {
    // Globbing socket files
    config.files.server.sockets.forEach(function (socketPath) {
        require(path.resolve(socketPath));
    });
}

/**
 * Configure error handling
 */
function initErrorRoutes(app) {
    // Assume 'not found' in the error msgs is a 404. this is somewhat silly, but valid, you can do whatever you like, set properties, use instanceof etc.
    app.use(function (err, req, res, next) {
        // If the error object doesn't exists
        if (!err) return next();

        // Log it
        logger.error(err);

        // send server error
        res.status(500).json({ status: 500, type: 'server-error', message: 'Unexpected server error' });
    });

    // Assume 404 since no middleware responded
    app.use(function (req, res) {
        // Send 404 with error message
        res.status(404).json({ status: 404, type: 'not-found', message: 'The resource was not found' });
    });
}

/**
 * Configure Socket.io
 */
function configureSocketIO(app, db) {
    // Load the Socket.io configuration
    var server = require(path.resolve('./src/server/lib/socket.io.js'))(app, db);

    // Return server object
    return server;
}

/**
 * Initialize the Express application
 */
module.exports.init = function (db) {

    // Initialize express app
    logger.info('Initializing Express');
    var app = express();

    // Initialize local variables
    initLocalVariables(app);

    // Initialize Express middleware
    initMiddleware(app);

    // Initialize Express view engine
    initViewEngine(app);

    // Initialize modules static client routes
    initModulesClientRoutes(app);

    // Initialize Express session
    initSession(app, db);

    // Initialize passport auth
    initPassport(app);

    // Initialize Modules configuration
    initModulesConfiguration(app);

    // Initialize Helmet security headers
    initHelmetHeaders(app);

    // Initialize modules server authorization policies
    initModulesServerPolicies(app);

    // Initialize modules server routes
    initModulesServerRoutes(app);

    // Initialize modules sockets
    initModulesServerSockets(app);

    // Initialize error routes
    initErrorRoutes(app);

    // Configure Socket.io
    app = configureSocketIO(app, db);

    return app;
};