node-rest-server-2
Version:
Configurable node rest server
756 lines (731 loc) • 25.8 kB
JavaScript
import express from 'express';
import chalk from 'chalk';
import DateFormat from 'date-fns/format';
import * as fs from 'fs';
import requestIp from 'request-ip';
import cors from 'cors';
import FastValidator from 'fastest-validator';
function _arrayLikeToArray(r, a) {
(null == a || a > r.length) && (a = r.length);
for (var e = 0, n = Array(a); e < a; e++) n[e] = r[e];
return n;
}
function _arrayWithoutHoles(r) {
if (Array.isArray(r)) return _arrayLikeToArray(r);
}
function asyncGeneratorStep(n, t, e, r, o, a, c) {
try {
var i = n[a](c),
u = i.value;
} catch (n) {
return void e(n);
}
i.done ? t(u) : Promise.resolve(u).then(r, o);
}
function _asyncToGenerator(n) {
return function () {
var t = this,
e = arguments;
return new Promise(function (r, o) {
var a = n.apply(t, e);
function _next(n) {
asyncGeneratorStep(a, r, o, _next, _throw, "next", n);
}
function _throw(n) {
asyncGeneratorStep(a, r, o, _next, _throw, "throw", n);
}
_next(void 0);
});
};
}
function _classCallCheck(a, n) {
if (!(a instanceof n)) throw new TypeError("Cannot call a class as a function");
}
function _defineProperties(e, r) {
for (var t = 0; t < r.length; t++) {
var o = r[t];
o.enumerable = o.enumerable || false, o.configurable = true, "value" in o && (o.writable = true), Object.defineProperty(e, _toPropertyKey(o.key), o);
}
}
function _createClass(e, r, t) {
return t && _defineProperties(e, t), Object.defineProperty(e, "prototype", {
writable: false
}), e;
}
function _defineProperty(e, r, t) {
return (r = _toPropertyKey(r)) in e ? Object.defineProperty(e, r, {
value: t,
enumerable: true,
configurable: true,
writable: true
}) : e[r] = t, e;
}
function _iterableToArray(r) {
if ("undefined" != typeof Symbol && null != r[Symbol.iterator] || null != r["@@iterator"]) return Array.from(r);
}
function _nonIterableSpread() {
throw new TypeError("Invalid attempt to spread non-iterable instance.\nIn order to be iterable, non-array objects must have a [Symbol.iterator]() method.");
}
function ownKeys(e, r) {
var t = Object.keys(e);
if (Object.getOwnPropertySymbols) {
var o = Object.getOwnPropertySymbols(e);
r && (o = o.filter(function (r) {
return Object.getOwnPropertyDescriptor(e, r).enumerable;
})), t.push.apply(t, o);
}
return t;
}
function _objectSpread2(e) {
for (var r = 1; r < arguments.length; r++) {
var t = null != arguments[r] ? arguments[r] : {};
r % 2 ? ownKeys(Object(t), true).forEach(function (r) {
_defineProperty(e, r, t[r]);
}) : Object.getOwnPropertyDescriptors ? Object.defineProperties(e, Object.getOwnPropertyDescriptors(t)) : ownKeys(Object(t)).forEach(function (r) {
Object.defineProperty(e, r, Object.getOwnPropertyDescriptor(t, r));
});
}
return e;
}
function _objectWithoutProperties(e, t) {
if (null == e) return {};
var o,
r,
i = _objectWithoutPropertiesLoose(e, t);
if (Object.getOwnPropertySymbols) {
var n = Object.getOwnPropertySymbols(e);
for (r = 0; r < n.length; r++) o = n[r], -1 === t.indexOf(o) && {}.propertyIsEnumerable.call(e, o) && (i[o] = e[o]);
}
return i;
}
function _objectWithoutPropertiesLoose(r, e) {
if (null == r) return {};
var t = {};
for (var n in r) if ({}.hasOwnProperty.call(r, n)) {
if (-1 !== e.indexOf(n)) continue;
t[n] = r[n];
}
return t;
}
function _regenerator() {
/*! regenerator-runtime -- Copyright (c) 2014-present, Facebook, Inc. -- license (MIT): https://github.com/babel/babel/blob/main/packages/babel-helpers/LICENSE */
var e,
t,
r = "function" == typeof Symbol ? Symbol : {},
n = r.iterator || "@@iterator",
o = r.toStringTag || "@@toStringTag";
function i(r, n, o, i) {
var c = n && n.prototype instanceof Generator ? n : Generator,
u = Object.create(c.prototype);
return _regeneratorDefine(u, "_invoke", function (r, n, o) {
var i,
c,
u,
f = 0,
p = o || [],
y = false,
G = {
p: 0,
n: 0,
v: e,
a: d,
f: d.bind(e, 4),
d: function (t, r) {
return i = t, c = 0, u = e, G.n = r, a;
}
};
function d(r, n) {
for (c = r, u = n, t = 0; !y && f && !o && t < p.length; t++) {
var o,
i = p[t],
d = G.p,
l = i[2];
r > 3 ? (o = l === n) && (u = i[(c = i[4]) ? 5 : (c = 3, 3)], i[4] = i[5] = e) : i[0] <= d && ((o = r < 2 && d < i[1]) ? (c = 0, G.v = n, G.n = i[1]) : d < l && (o = r < 3 || i[0] > n || n > l) && (i[4] = r, i[5] = n, G.n = l, c = 0));
}
if (o || r > 1) return a;
throw y = true, n;
}
return function (o, p, l) {
if (f > 1) throw TypeError("Generator is already running");
for (y && 1 === p && d(p, l), c = p, u = l; (t = c < 2 ? e : u) || !y;) {
i || (c ? c < 3 ? (c > 1 && (G.n = -1), d(c, u)) : G.n = u : G.v = u);
try {
if (f = 2, i) {
if (c || (o = "next"), t = i[o]) {
if (!(t = t.call(i, u))) throw TypeError("iterator result is not an object");
if (!t.done) return t;
u = t.value, c < 2 && (c = 0);
} else 1 === c && (t = i.return) && t.call(i), c < 2 && (u = TypeError("The iterator does not provide a '" + o + "' method"), c = 1);
i = e;
} else if ((t = (y = G.n < 0) ? u : r.call(n, G)) !== a) break;
} catch (t) {
i = e, c = 1, u = t;
} finally {
f = 1;
}
}
return {
value: t,
done: y
};
};
}(r, o, i), true), u;
}
var a = {};
function Generator() {}
function GeneratorFunction() {}
function GeneratorFunctionPrototype() {}
t = Object.getPrototypeOf;
var c = [][n] ? t(t([][n]())) : (_regeneratorDefine(t = {}, n, function () {
return this;
}), t),
u = GeneratorFunctionPrototype.prototype = Generator.prototype = Object.create(c);
function f(e) {
return Object.setPrototypeOf ? Object.setPrototypeOf(e, GeneratorFunctionPrototype) : (e.__proto__ = GeneratorFunctionPrototype, _regeneratorDefine(e, o, "GeneratorFunction")), e.prototype = Object.create(u), e;
}
return GeneratorFunction.prototype = GeneratorFunctionPrototype, _regeneratorDefine(u, "constructor", GeneratorFunctionPrototype), _regeneratorDefine(GeneratorFunctionPrototype, "constructor", GeneratorFunction), GeneratorFunction.displayName = "GeneratorFunction", _regeneratorDefine(GeneratorFunctionPrototype, o, "GeneratorFunction"), _regeneratorDefine(u), _regeneratorDefine(u, o, "Generator"), _regeneratorDefine(u, n, function () {
return this;
}), _regeneratorDefine(u, "toString", function () {
return "[object Generator]";
}), (_regenerator = function () {
return {
w: i,
m: f
};
})();
}
function _regeneratorDefine(e, r, n, t) {
var i = Object.defineProperty;
try {
i({}, "", {});
} catch (e) {
i = 0;
}
_regeneratorDefine = function (e, r, n, t) {
function o(r, n) {
_regeneratorDefine(e, r, function (e) {
return this._invoke(r, n, e);
});
}
r ? i ? i(e, r, {
value: n,
enumerable: !t,
configurable: !t,
writable: !t
}) : e[r] = n : (o("next", 0), o("throw", 1), o("return", 2));
}, _regeneratorDefine(e, r, n, t);
}
function _toConsumableArray(r) {
return _arrayWithoutHoles(r) || _iterableToArray(r) || _unsupportedIterableToArray(r) || _nonIterableSpread();
}
function _toPrimitive(t, r) {
if ("object" != typeof t || !t) return t;
var e = t[Symbol.toPrimitive];
if (void 0 !== e) {
var i = e.call(t, r);
if ("object" != typeof i) return i;
throw new TypeError("@@toPrimitive must return a primitive value.");
}
return ("string" === r ? String : Number)(t);
}
function _toPropertyKey(t) {
var i = _toPrimitive(t, "string");
return "symbol" == typeof i ? i : i + "";
}
function _typeof(o) {
"@babel/helpers - typeof";
return _typeof = "function" == typeof Symbol && "symbol" == typeof Symbol.iterator ? function (o) {
return typeof o;
} : function (o) {
return o && "function" == typeof Symbol && o.constructor === Symbol && o !== Symbol.prototype ? "symbol" : typeof o;
}, _typeof(o);
}
function _unsupportedIterableToArray(r, a) {
if (r) {
if ("string" == typeof r) return _arrayLikeToArray(r, a);
var t = {}.toString.call(r).slice(8, -1);
return "Object" === t && r.constructor && (t = r.constructor.name), "Map" === t || "Set" === t ? Array.from(r) : "Arguments" === t || /^(?:Ui|I)nt(?:8|16|32)(?:Clamped)?Array$/.test(t) ? _arrayLikeToArray(r, a) : void 0;
}
}
var dateTime = function dateTime() {
return DateFormat(Date.now(), 'dd-MM-yyyy HH-mm-ss');
};
var appName = function appName() {
return '[node-rest-server]';
};
var isDebug = false;
var isEnabled = true;
var getValue = function getValue(value, defaultValue) {
return value === undefined ? defaultValue : value;
};
var getMessage = function getMessage(type, message) {
return {
appName: appName(),
level: type.toUpperCase(),
timestamp: dateTime(),
message: message.join(' ')
};
};
var print = function print(color, type) {
if (isEnabled) {
for (var _len = arguments.length, message = new Array(_len > 2 ? _len - 2 : 0), _key = 2; _key < _len; _key++) {
message[_key - 2] = arguments[_key];
}
var jsonMessage = getMessage(type, message);
var formattedLog = chalk[color](jsonMessage.appName, '-', jsonMessage.timestamp, '-', jsonMessage.level, '\t-', jsonMessage.message);
console[type](formattedLog);
}
};
var logger = {
log: function log() {
for (var _len2 = arguments.length, message = new Array(_len2), _key2 = 0; _key2 < _len2; _key2++) {
message[_key2] = arguments[_key2];
}
return print.apply(void 0, ['green', 'log'].concat(message));
},
info: function info() {
for (var _len3 = arguments.length, message = new Array(_len3), _key3 = 0; _key3 < _len3; _key3++) {
message[_key3] = arguments[_key3];
}
return print.apply(void 0, ['green', 'info'].concat(message));
},
warn: function warn() {
for (var _len4 = arguments.length, message = new Array(_len4), _key4 = 0; _key4 < _len4; _key4++) {
message[_key4] = arguments[_key4];
}
return print.apply(void 0, ['yellow', 'warn'].concat(message));
},
debug: function debug() {
if (isDebug) {
for (var _len5 = arguments.length, message = new Array(_len5), _key5 = 0; _key5 < _len5; _key5++) {
message[_key5] = arguments[_key5];
}
print.apply(void 0, ['gray', 'debug'].concat(message));
}
},
error: function error() {
for (var _len6 = arguments.length, message = new Array(_len6), _key6 = 0; _key6 < _len6; _key6++) {
message[_key6] = arguments[_key6];
}
return print.apply(void 0, ['red', 'error'].concat(message));
},
trace: function trace() {
for (var _len7 = arguments.length, message = new Array(_len7), _key7 = 0; _key7 < _len7; _key7++) {
message[_key7] = arguments[_key7];
}
return print.apply(void 0, ['red', 'trace'].concat(message));
}
};
var initializeLogger = function initializeLogger(_ref) {
var logger = _ref.logger;
if (typeof logger === 'boolean') {
isDebug = false;
isEnabled = logger;
} else if (_typeof(logger) === 'object') {
isDebug = getValue(logger.debug, false);
isEnabled = getValue(logger.enable, true);
}
};
/* eslint-enable no-console */
var GLOBAL_API_ERROR = 500;
var extractIfAvailable = function extractIfAvailable(object, attributes) {
if (object[attributes]) return _defineProperty({}, attributes, object[attributes]);
if (Array.isArray(attributes)) {
return attributes.reduce(function (result, attribute) {
if (object[attribute]) result[attribute] = object[attribute];
}, {});
}
return;
};
var getRequestData = function getRequestData(request) {
return {
url: "".concat(request.protocol, "://").concat(request.hostname).concat(request.originalUrl),
body: request.body,
pathParams: request.params,
queryParams: request.query,
getHeader: request.get,
headers: request.headers,
method: request.method,
clientIp: request.clientIp
};
};
var getFilterData = function getFilterData(response) {
return {
filter: response.locals
};
};
var getControllerOptions = function getControllerOptions(options) {
var sanitisedOptions = extractIfAvailable(options, 'getDatabaseConnection');
return sanitisedOptions || {};
};
var _excluded = ["status", "payload"];
var ResponseHandler = /*#__PURE__*/function () {
function ResponseHandler() {
_classCallCheck(this, ResponseHandler);
}
return _createClass(ResponseHandler, null, [{
key: "getResponseData",
value: function getResponseData(routeConfig) {
var responseData = arguments.length > 1 && arguments[1] !== undefined ? arguments[1] : {};
var status = responseData.status,
payload = responseData.payload,
userData = _objectWithoutProperties(responseData, _excluded);
return {
status: status || routeConfig.status || 200,
data: payload || userData
};
}
}]);
}();
var ErrorHandler = {
fn: null,
registerDevHandler: function registerDevHandler(app) {
if (app.get('env') === 'development') {
logger.debug('Loading Error handler');
}
},
registerErrorHandler: function registerErrorHandler(fn) {
if (typeof fn === 'function') {
logger.debug('Loading Error handler');
ErrorHandler.fn = fn;
}
}
};
var errorHandler = function errorHandler(err) {
logger.error(JSON.stringify(err));
if (typeof ErrorHandler.fn === 'function') {
ErrorHandler.fn(err);
}
};
var publishResponse = function publishResponse(response, status, data) {
var hasFilePath = false;
var hasHtml = false;
try {
if (data && _typeof(data) === 'object') {
hasFilePath = 'filePath' in data;
hasHtml = 'html' in data && 'isHtml' in data && data.isHtml === true;
}
} catch (e) {}
// If there is a file, return that
if (hasFilePath) {
if (fs.existsSync(data.filePath)) {
response.sendFile(data.filePath);
} else {
response.end();
}
return;
}
// If there is HTML, return that
if (hasHtml) {
response.setHeader('Content-Type', 'text/html');
response.send(data.html);
return;
}
// If there is a status and data, return that
// JSON when required
if (status && data && Object.keys(data).length !== 0) {
response.status(status).json(data);
} else if (status && (!data || Object.keys(data).length === 0)) {
response.status(status).end();
} else {
response.end();
}
};
/**
* Custom publish will NOT end the response. This will allow you to use the response object to send custom data.
* @param response
* @param status
* @param data
*/
var publishCustom = function publishCustom(response, status, data) {
response.status(status);
};
var sendResponse = function sendResponse(routeConfig, responseData, response, serverConfig) {
var _ResponseHandler$getR = ResponseHandler.getResponseData(routeConfig, responseData, serverConfig),
status = _ResponseHandler$getR.status,
data = _ResponseHandler$getR.data;
logger.info('Response sent : ', JSON.stringify(data));
var publish = function publish() {
if ((routeConfig === null || routeConfig === void 0 ? void 0 : routeConfig.customResponse) === true) {
publishCustom(response, status);
} else {
publishResponse(response, status, data);
}
};
if (serverConfig.delay > 0) {
setTimeout(function () {
publish();
}, serverConfig.delay * 1000);
} else {
publish();
}
};
var handleControllerResponse = function handleControllerResponse(routeConfig, controllerOptions, request, response, next) {
if (typeof routeConfig.controller === 'function') {
var requestData = _objectSpread2(_objectSpread2({}, getRequestData(request)), getFilterData(response));
return routeConfig.controller(requestData, controllerOptions, request, response, next);
} else if (_typeof(routeConfig.controller) === 'object') {
return routeConfig.controller;
}
return;
};
var RouteProvider = (function (routeConfig, controllerOptions, serverConfig) {
return function (request, response, next) {
try {
var responseData = handleControllerResponse(routeConfig, controllerOptions, request, response, next);
if (responseData instanceof Promise) {
responseData.then(function (data) {
sendResponse(routeConfig, data, response, next, serverConfig);
}, function (error) {
errorHandler(error);
publishResponse(response, GLOBAL_API_ERROR, error.message);
});
return;
}
sendResponse(routeConfig, responseData, response, serverConfig);
} catch (error) {
errorHandler(error);
publishResponse(response, GLOBAL_API_ERROR, error.message);
}
};
});
var MiddlewareProvider = /*#__PURE__*/function () {
function MiddlewareProvider() {
_classCallCheck(this, MiddlewareProvider);
}
return _createClass(MiddlewareProvider, null, [{
key: "registerRequestLogger",
value: function registerRequestLogger(app) {
logger.debug('Registering request logger');
app.use(function (request, response, next) {
var data = getRequestData(request);
logger.info('Request URL : ', JSON.stringify(data.url));
logger.info('Request headers : ', JSON.stringify(data.headers));
logger.info('Request body : ', JSON.stringify(data.body));
next();
});
}
}, {
key: "registerIpMiddleware",
value: function registerIpMiddleware(app) {
logger.debug('Registering ip middleware');
app.use(requestIp.mw());
}
}, {
key: "registerFilters",
value: function registerFilters(app, serverConfig) {
logger.debug('Registering global filter');
app.use(function (request, response, next) {
var data = getRequestData(request);
if (typeof serverConfig.filter === 'function') {
logger.info('Executing filter...');
var filterData = serverConfig.filter(data);
if (filterData instanceof Promise) {
filterData.then(function (filterDataResponse) {
response.locals = filterDataResponse || {};
next();
}, errorHandler);
return;
}
response.locals = filterData || {};
}
next();
});
}
}, {
key: "registerStatusEndpoint",
value: function registerStatusEndpoint(app) {
logger.debug('Registering /status endpoint to get routes information');
app.get('/status', function (request, response) {
response.send(app._router.stack);
});
}
}]);
}();
var configProcessor = function configProcessor(app, serverConfig) {
app.set('port', serverConfig.port || 8000);
app.set('x-powered-by', false);
};
var registerPreprocessor = function registerPreprocessor(app, serverConfig) {
logger.debug('loading json processor');
app.use(express.json());
logger.debug('loading URL encoder');
app.use(express.urlencoded({
extended: true
}));
logger.debug('loading cors request handler');
app.use(cors(serverConfig.cors));
};
var initPreProcessors = function initPreProcessors(app, serverConfig) {
configProcessor(app, serverConfig);
registerPreprocessor(app, serverConfig);
};
var serverSettingsSchema = {
$$root: true,
strict: true,
type: 'object',
optional: true,
messages: {
objectStrict: "Server settings contains forbidden keys: '{actual}', valid properties are '{expected}'"
},
props: {
basePath: {
type: 'string',
"default": ''
},
port: {
type: 'number',
positive: true,
integer: true,
"default": 8000
},
delay: {
type: 'number',
positive: true,
integer: true,
"default": 0
},
logger: {
type: 'multi',
rules: [{
type: 'boolean'
}, {
type: 'object',
props: {
enable: {
type: 'boolean',
"default": true
},
debug: {
type: 'boolean',
"default": false
}
}
}],
"default": true
},
onError: {
type: 'function',
optional: true
},
filter: {
type: 'function',
optional: true
},
cors: {
type: 'any',
optional: true
},
middlewares: {
type: 'array',
optional: true
},
getDatabaseConnection: {
type: 'function',
optional: true
}
}
};
var validator = new FastValidator();
var serverSettingsValidator = validator.compile(serverSettingsSchema);
var ERROR = {
VALIDATION_MESSAGE: 'occurred during validation of'
};
var commonResultProcessor = function commonResultProcessor(result, type) {
if (result !== true) {
var formattedMessages = result.map(function (_ref) {
var message = _ref.message;
return "\n".concat(message);
});
throw Error(["".concat(ERROR.VALIDATION_MESSAGE, " ").concat(type)].concat(_toConsumableArray(formattedMessages)).join(''));
}
};
var validateServerSettings = function validateServerSettings(serverConfig) {
logger.info('Validating Server settings');
var validationStatus = serverSettingsValidator(serverConfig);
commonResultProcessor(validationStatus, 'server settings');
};
var hasUniqueMethods = function hasUniqueMethods() {
var endpointList = arguments.length > 0 && arguments[0] !== undefined ? arguments[0] : [];
return endpointList.map(function (endpointHandler) {
return endpointHandler.method;
}).filter(function (method, index, methods) {
return methods.indexOf(method) === index;
}).length === endpointList.length;
};
var registerMethod = function registerMethod(app, endpoint, endpointHandlerConfigItem, controllerOptions, serverConfig) {
var uri = "".concat(serverConfig.basePath || '').concat(endpoint);
if (typeof endpointHandlerConfigItem.method === 'string') {
var method = String(endpointHandlerConfigItem.method);
logger.info('Registering route path:', method.toUpperCase(), uri);
app[method.toLowerCase()](uri, RouteProvider(endpointHandlerConfigItem, controllerOptions, serverConfig));
}
};
var NodeRestServer = /*#__PURE__*/function () {
var _ref = _asyncToGenerator(/*#__PURE__*/_regenerator().m(function _callee(app, routeConfig) {
var serverConfig,
_serverConfig$middlew,
controllerOptions,
customMiddleWares,
customMiddleware,
_args = arguments;
return _regenerator().w(function (_context) {
while (1) switch (_context.n) {
case 0:
serverConfig = _args.length > 2 && _args[2] !== undefined ? _args[2] : {};
try {
validateServerSettings(serverConfig);
logger.info('Loading resources and starting server', serverConfig);
logger.debug('initializing application logger with', JSON.stringify(serverConfig.logger));
initializeLogger(serverConfig);
logger.info('Applying preprocessors');
initPreProcessors(app, serverConfig);
logger.info('Applying global middlewares');
MiddlewareProvider.registerRequestLogger(app);
MiddlewareProvider.registerIpMiddleware(app);
MiddlewareProvider.registerFilters(app, serverConfig);
MiddlewareProvider.registerStatusEndpoint(app);
controllerOptions = getControllerOptions(serverConfig);
logger.debug('Applying custom global middlewares');
customMiddleWares = (_serverConfig$middlew = serverConfig === null || serverConfig === void 0 ? void 0 : serverConfig.middlewares) !== null && _serverConfig$middlew !== void 0 ? _serverConfig$middlew : [];
if (customMiddleWares.length > 0) {
for (customMiddleware in customMiddleWares) {
if (typeof customMiddleware === 'function') {
app.use(customMiddleware());
}
}
}
Object.keys(routeConfig).forEach(function (endpoint) {
var endpointHandlerConfigs = routeConfig[endpoint];
if (Array.isArray(endpointHandlerConfigs)) {
if (hasUniqueMethods(endpointHandlerConfigs)) {
endpointHandlerConfigs.forEach(function (endpointHandlerConfigItem) {
return registerMethod(app, endpoint, endpointHandlerConfigItem, controllerOptions, serverConfig);
});
} else {
logger.error('Multiple handlers for same http method found for endpoint : ', endpoint);
}
} else {
registerMethod(app, endpoint, endpointHandlerConfigs, controllerOptions, serverConfig);
}
});
ErrorHandler.registerDevHandler(app);
if (typeof serverConfig.onError !== 'undefined') {
ErrorHandler.registerErrorHandler(serverConfig.onError);
}
} catch (error) {
logger.error(error);
if (typeof serverConfig.onError !== 'undefined') {
serverConfig.onError(error);
}
}
case 1:
return _context.a(2);
}
}, _callee);
}));
return function NodeRestServer(_x, _x2) {
return _ref.apply(this, arguments);
};
}();
export { NodeRestServer as default };