simple-node-framework
Version:
Simple nodeJs framework that provides easy ways to use log, cache, database, session, redis, share request scope and more.
278 lines (232 loc) • 8.88 kB
JavaScript
const express = require('express');
const bunyan = require('bunyan');
const os = require('os');
const Base = require('./base/base');
const Cache = require('./cache');
const Session = require('./session');
const config = require('./config');
const Authorization = require('./authorization').class;
const origin = require('./plugins/origin').instance;
const logger = require('./log').instance;
const route = require('./route').instance;
const database = require('./database').instance;
const redis = require('./redis').instance;
const queue = require('./queue').instance;
const errorHandler = require('./error').instance;
const compression = require('compression')
const bodyParser = require('body-parser')
const RequestAndResponseLogger = require('./plugins/request-response-logger');
// this class abstracts the web server concept.
class Server extends Base {
constructor({ module = 'SNF Server' } = {}) {
super({
module
});
this.config = config;
this.app = null;
this.origin = origin;
this.route = route;
this.module = module;
this.healthCheckUrl = this.config.app.healthCheck || '/';
this.middlewares = {
bodyParser: bodyParser,
compression,
logMiddleware: (req, res, next) => {
req.log = logger;
next();
}
}
this.plugins = {
requestAndResponseLogger: new RequestAndResponseLogger()
}
}
// configure server, middlewares, cors, error handle and audit
configure(options = { port: 8080, httpsOptions: null, listenCallBack: null, afterListenCallback: null }) {
this.baseServer = express;
this.app = express();
this.configureMiddlewares();
this.listen(options.port, options.listenCallBack, options.afterListenCallback);
return {
app: this.app,
server: this.server,
baseServer: this.baseServer
}
}
// configure webserver events
configureWebServer() {
// close server event
process.on('exit', () => {
this.log.debug('exit');
this.log.debug('Bye bye!');
});
process.on('SIGINT', () => {
this.log.debug('SIGINT');
this.close()
});
process.on('SIGTERM', () => {
this.log.debug('SIGTERM');
this.close()
});
}
close() {
this.server.close(() => {
this.log.debug(`The application has been terminated`);
this.log.debug('Closing servers and connections...');
database.close();
redis.close();
queue.close();
this.log.debug('Done!');
})
}
// override this method to include or change the order of your middleware and plugins
configureMiddlewares() {
this.configureCors();
// override the send and json methods to store the response body
this.app.use((req, res, next) => {
const originalSend = res.send;
const originalJson = res.json;
res.send = function (body) {
res._data = body
return originalSend.call(this, body);
};
res.json = function (body) {
res._data = body;
return originalJson.call(this, body);
};
next();
});
// applying the web server plugins
this.app.use(this.middlewares.logMiddleware)
this.app.use(Authorization.parse);
this.app.use(this.middlewares.compression(this.config.middlewares?.compression));
this.app.use(this.middlewares.bodyParser.json(this.config.middlewares?.bodyParser?.json));
// enable the automatic request and response logs
this.app.use(this.plugins.requestAndResponseLogger.write.bind(this.plugins.requestAndResponseLogger))
// enable the limit of the quantity of requests per user in a time interval
//if (this.config.throttle) this.app.use(this.baseServer.plugins.throttle(this.config.throttle)); //???????
this.configureCache();
this.configureSession();
// enable the origin plugin
if (this.origin) this.app.use(this.origin.proccess.bind(this.origin));
}
// configure application routes
configureRoutes() {
this.route.importModuleRoutes();
this.configureHealthCheck();
}
// configure health check route
configureHealthCheck() {
this.app.get(this.healthCheckUrl, (req, res, next) => {
res.status(200).send(`${this.config.app.name} is running with ${this.config.app.env} environment on ${os.hostname()}`);
return next();
});
}
// configure Cross-Origin Resource Sharing (CORS)
// [https://developer.mozilla.org/pt-BR/docs/Web/HTTP/Controle_Acesso_CORS] [https://www.npmjs.com/package/cors]
configureCors() {
if (this.config.cors) {
this.log.debug('Cors has been configured');
const cors = require('cors'); // eslint-disable-line
this.app.use(cors(this.config.cors));
}
}
// configure the error handle
configureErrorHandler() {
process.on('uncaughtException', error => {
this.logger.error('Uncaught Exception', error.message, {
error: {
name: error.name,
message: error.message,
stack: error.stack,
},
})
})
process.on('unhandledRejection', error => {
this.logger.debug('Unhandled Rejection', error.message, {
error: {
name: error.name,
message: error.message,
stack: error.stack,
},
})
})
this.app.use((err, req, res, next) => {
const statusCode = err.statusCode || 500;
const response = {
"code": err.code,
"name": err.name,
"message": err.message
}
res.status(statusCode).send(response);
});
}
// configure and connect to the database
configureDatabase() {
database.connect();
}
// configure and connect to the redis
configureRedis() {
redis.connect();
}
// configure and connect to the queue
async configureQueue() {
await queue.connect();
}
// configure and cache if the redis is enabled
configureCache() {
if (this.config.redis && this.config.cache && this.config.cache.enabled) {
this.app.use((req, res, next) => {
req.cache = new Cache();
return next();
});
this.log.debug('Cache has been configured');
}
}
// configure session if the redis is enabled
configureSession() {
if (this.config.redis && this.config.session) {
this.app.use(async (req, res, next) => {
new Session(req)
.load()
.then(() => {
return next();
})
.catch((error) => {
this.log.error('Unespected error on session load', error);
return next('Unespected error on session load');
});
});
this.log.debug('Session has been configured');
}
}
configureStartupProbe() {
this.app.get('/snf/startup-probe', (_req, res) => {
const isReady = database.isReady() && redis.isReady();
if (isReady) {
return res.status(200).json({ ok: true, message: 'Application is ready!' })
}
return res.status(503).json({
ok: false,
message: 'Application is not ready yet! Please wait a few seconds and try again.'
})
})
}
// start web-server listen
listen(port, listenCallBack, afterListenCallback) {
const _port = this.config.app.port || port;
const callback = () => {
this.log.debug(`The sandbox is running as [${config.app.env}] [http://localhost:${_port}${this.healthCheckUrl}]`);
this.configureDatabase();
this.configureRedis();
this.configureQueue();
this.configureRoutes(); // ATTENTION: configureRoutes have to be after configureDatabase
this.configureErrorHandler();
this.configureWebServer();
this.configureStartupProbe();
if (afterListenCallback) afterListenCallback();
};
this.server = this.app.listen(_port, listenCallBack || callback);
return this.server
}
}
module.exports = Server;