diff --git a/server.js b/server.js index bab3aab..858625f 100644 --- a/server.js +++ b/server.js @@ -1,19 +1,64 @@ const express = require('express'); +const cluster = require('cluster'); +const numCores = require('os').cpus().length; +const chalk = require('chalk'); const morgan = require('morgan'); const clientSession = require('client-sessions'); +const cookieParser = require('cookie-parser'); +const rateLimit = require('express-rate-limit'); const helmet = require('helmet'); +const mongoSanitize = require('express-mongo-sanitize'); +const xss = require('xss-clean'); +const hpp = require('hpp'); +const cors = require('cors'); +const compression = require('compression'); const {SESSION_SECRET} = require('./config'); const app = express(); const api = require('./src/api'); -app.get('/', (request, response) => response.sendStatus(200)); -app.get('/health', (request, response) => response.sendStatus(200)); +app.enable('trust proxy'); + +// Set Body parser, reading data from body into req.body +app.use(express.json({ limit: '10kb' })); +app.use(express.urlencoded({ extended: true, limit: '10kb' })); + +// Set Cookie parser +app.use(cookieParser()); + +// Set security HTTP headers +app.use(helmet()); + +//Limit requests from the same API +const limiter = rateLimit({ + max: 100, + windowMs: 60 * 60 * 1000, + messege: 'Too many requests from this IP, Please try again in an hour!' +}); +app.use('/api', limiter); + +//Date sanitization against NoSQL query injection +app.use(mongoSanitize()); + +//Data sanitization against XSS +app.use(xss()); + +// Prevent http param pollution +app.use(hpp()); + +// Implement CORS +app.use(cors()); + +app.options('*', cors()); + +app.use(compression()); + +app.disable('x-powered-by'); + +app.use(morgan('dev')); -app.use(morgan('short')); -app.use(express.json()); app.use( clientSession({ cookieName: 'session', @@ -21,19 +66,100 @@ app.use( duration: 24 * 60 * 60 * 1000 }) ); -app.use(helmet()); + +app.get('/', (request, response) => response.sendStatus(200)); +app.get('/health', (request, response) => response.sendStatus(200)); app.use(api); -let server; -module.exports = { - start(port) { - server = app.listen(port, () => { - console.log(`App started on port ${port}`); +// Handle uncaught exceptions +process.on('uncaughtException', (uncaughtExc) => { + // Won't execute + console.log(chalk.bgRed('UNCAUGHT EXCEPTION! 💥 Shutting down...')); + console.log('uncaughtException Err::', uncaughtExc); + console.log('uncaughtException Stack::', JSON.stringify(uncaughtExc.stack)); + process.exit(1); +}); + +// Setup number of worker processes to share port which will be defined while setting up server +const workers = []; +const setupWorkerProcesses = () => { + // Read number of cores on system + console.log(`Master cluster setting up ${numCores} workers`); + + // Iterate on number of cores need to be utilized by an application + // Current example will utilize all of them + for (let i = 0; i < numCores; i++) { + // Creating workers and pushing reference in an array + // these references can be used to receive messages from workers + workers.push(cluster.fork()); + + // Receive messages from worker process + workers[i].on('message', function (message) { + console.log(message); + }); + } + + // Process is clustered on a core and process id is assigned + cluster.on('online', function (worker) { + console.log(`Worker ${worker.process.pid} is listening`); + }); + + // If any of the worker process dies then start a new one by simply forking another one + cluster.on('exit', function (worker, code, signal) { + console.log( + `Worker ${worker.process.pid} died with code: ${code}, and signal: ${signal}` + ); + console.log('Starting a new worker'); + cluster.fork(); + workers.push(cluster.fork()); + // Receive messages from worker process + workers[workers.length - 1].on('message', function (message) { + console.log(message); + }); + }); +}; + +// Setup an express server and define port to listen all incoming requests for this application +const setUpExpress = () => { + const server = app.listen(3000, () => { + console.log(`App running on port ${chalk.greenBright(port)}...`); + }); + + // In case of an error + app.on('error', (appErr, appCtx) => { + console.error('app error', appErr.stack); + console.error('on url', appCtx.req.url); + console.error('with headers', appCtx.req.headers); + }); + + // Handle unhandled promise rejections + process.on('unhandledRejection', (err) => { + console.log(chalk.bgRed('UNHANDLED REJECTION! 💥 Shutting down...')); + console.log(err.name, err.message); + // Close server & exit process + server.close(() => { + process.exit(1); }); - return app; - }, - stop() { - server.close(); + }); + + process.on('SIGTERM', () => { + console.log('👋 SIGTERM RECEIVED. Shutting down gracefully'); + server.close(() => { + console.log('💥 Process terminated!'); + }); + }); +}; + +// Setup server either with clustering or without it +const setupServer = (isClusterRequired) => { + // If it is a master process then call setting up worker process + if (isClusterRequired && cluster.isMaster) { + setupWorkerProcesses(); + } else { + // Setup server configurations and share port address for incoming requests + setUpExpress(); } }; + +module.exports = setupServer(true);