kunagpal/express-boilerplate

View on GitHub
app.js

Summary

Maintainability
A
2 hrs
Test Coverage
/**
* @file The application bootstrapper.
*/
 
/* eslint-disable no-process-env */
global._ = require('lodash');
global.path = require('path');
global.utils = require('./utils/misc');
 
var fs = require('fs'),
 
cors = require('cors'),
async = require('async'),
csurf = require('csurf'),
logger = require('morgan'),
helmet = require('helmet'),
express = require('express'),
load = require('require-all'),
passport = require('passport'),
static = require('serve-static'),
bodyParser = require('body-parser'),
compression = require('compression'),
cookieParser = require('cookie-parser'),
mongodb = require('mongodb').MongoClient,
expressSession = require('express-session'),
errorHandler = require('raven').errorHandler,
 
pack = require('./scripts/misc/pack'),
 
port,
app = express(),
env = process.env.NODE_ENV, // repeated process.env related operations are expensive, so cache once and reuse
isLive = env && !_.includes(['development', 'test'], env),
name = process.env.npm_package_name || require('./package').name; // eslint-disable-line global-require
 
isLive ? utils.checkVars() : require('dotenv').load();
 
// view engine setup
app.set('title', name);
app.set('view engine', 'ejs');
app.enable('trust proxy');
app.set('views', 'views');
app.set('port', port = Number(process.env.PORT) || 3000);
 
app.use(helmet());
app.use(compression());
app.use('/api', cors({ origin: false }), function (req, res, next) {
var accept = req.get('accept');
 
// If the Accept header is generic or unspecified, set it so that API responses are JSON by default.
// This is necessary so that the app error handling middleware below can be reused for all kinds of requests.
((accept === '*/*') || !accept) && (req.headers.accept = 'application/json');
 
next();
});
 
(env !== 'test') && app.use(logger('dev'));
app.use('/static', static(path.resolve('public/min')));
 
app.use(bodyParser.json());
app.use(bodyParser.urlencoded({ extended: false }));
 
app.use(cookieParser(process.env.COOKIE_SECRET, { signed: true }));
 
app.use(expressSession({ secret: process.env.SESSION_SECRET, resave: false, saveUninitialized: false }));
 
app.use(function (req, res, next) {
!req.session.flash && (req.session.flash = []);
 
/**
* An elementary flash message handler to interface across the back and front ends via a series of flash messages.
*
* @param {String} content - A flash message to displayed on the front end.
* @returns {Number|String} - A stub returned to indicate the message displayed or the current length of the queue.
*/
res.flash = function (content) {
return content ? req.session.flash.push(content) : req.session.flash.pop();
};
 
next();
});
 
app.use(passport.initialize());
app.use(passport.session());
 
Function `exports` has 73 lines of code (exceeds 25 allowed). Consider refactoring.
module.exports = function (done) {
mongodb.connect(process.env.MONGO_URI || `mongodb://127.0.0.1:27017/${_.kebabCase(name)}${env ? '-' + env : ''}`,
{ w: 1 }, function (mongoError, db) {
if (mongoError) { throw mongoError; }
 
(env === 'test') && _.assign(global.testUtils.db, {
close: db.close.bind(db),
purge: function (next) {
db.dropDatabase(next); // bind has not been used here for test performance reasons
}
});
_.forEach(fs.readdirSync('database'), function (model) { // eslint-disable-line no-sync
// This ensures that the models can't be messed with.
(model = path.parse(model).name) && Object.defineProperty(global, model, {
value: utils.makeModel(model, db), // eslint-disable-line global-require
configurable: false,
writable: false,
enumerable: true
});
});
 
var server,
routes = load(path.resolve('routes')),
 
/**
* Handles error and SIGINT events. No errors should be thrown here, just appropriate use of
* process.exit.
*
* @param {?Error} err - An error object, optionally passed on from the error event.
*/
handle = function (err) {
async.series([
function (next) {
server.close(next);
},
async.apply(db.close.bind(db), true)
], function (handleError) {
// and error from server/db close is non fatal in this context, simply log it
handleError && console.error(handleError);
 
// if this handle was invoked for a fatal error, like an uncaughtException instead, log the
// error as usual, but reactively exit instead.
err && console.error(err) && (process.exitCode = 1);
process.exit();
});
};
 
process.on('SIGINT', handle);
process.on('SIGTERM', handle);
process.on('uncaughtException', handle);
 
app.use('/api', routes.api) && delete routes.api;
app.use(csurf());
 
_.forEach(routes, function (router, mountPoint) {
app.use(`/${mountPoint === 'index' ? '' : mountPoint}`, router);
});
 
// catch 404 and forward to error handler
app.use(function (req, res, next) {
var err = new Error('Not Found');
 
err.status = 404;
next(err);
});
 
// error handlers
isLive && app.use(errorHandler(process.env.SENTRY_DSN));
 
// eslint-disable-next-line no-unused-vars
app.use(function (err, req, res, next) { // the last argument is necessary
var error = {
status: Number(err.status) || 500
};
 
err.name && (error.name = err.name);
res.status(error.status);
 
// _.assign has not been used here to avoid creating a new object with the stack and error message.
if (!isLive) { // Pass the complete error stack and message to the response if running in a dev/test env
error.stack = err.stack;
error.message = err.message;
}
 
// can't use conditional _.omit on err here as details will be lost
return res.format({
html: function () {
res.render('error', { error: error });
},
json: function () {
res.json({ error: error });
},
default: function () {
// set the status code and it's corresponding human friendly message in one go.
res.sendStatus(406);
}
});
});
 
pack(function (err) {
if (err) { throw err; } // An error in static asset compression is usually fatal, abort app load
server = app.listen(port, done);
server.on('error', handle);
});
});
};
 
!module.parent && module.exports(_.noop);