import express, { ErrorRequestHandler, RequestHandler, json } from "express"; import compression from "compression"; import fileUpload from "express-fileupload"; import https from "https"; import { Logger } from "winston"; import { AppConfig, CommonConfig, ServerConfig } from "./config-type"; import { ResultHandlerError } from "./errors"; import { isLoggerConfig } from "./common-helpers"; import { createLogger } from "./logger"; import { defaultResultHandler, lastResortHandler } from "./result-handler"; import { initRouting, Routing } from "./routing"; import createHttpError from "http-errors"; type AnyResultHandler = NonNullable<CommonConfig["errorHandler"]>; export const createParserFailureHandler = (errorHandler: AnyResultHandler, logger: Logger): ErrorRequestHandler => (error, request, response, next) => { if (!error) { return next(); } errorHandler.handler({ error, request, response, logger, input: request.body, output: null, }); }; export const createNotFoundHandler = (errorHandler: AnyResultHandler, logger: Logger): RequestHandler => (request, response) => { const error = createHttpError( 404, `Can not ${request.method} ${request.path}` ); try { errorHandler.handler({ request, response, logger, error, input: null, output: null, }); } catch (e) { if (e instanceof Error) { lastResortHandler({ response, logger, error: new ResultHandlerError(e.message, error), }); } } }; export function attachRouting( config: AppConfig & CommonConfig, routing: Routing ) { const logger = isLoggerConfig(config.logger) ? createLogger(config.logger) : config.logger; initRouting({ app: config.app, routing, logger, config }); const errorHandler = config.errorHandler || defaultResultHandler; const notFoundHandler = createNotFoundHandler(errorHandler, logger); return { notFoundHandler, logger }; } export function createServer( config: ServerConfig & CommonConfig, routing: Routing ) { const logger = isLoggerConfig(config.logger) ? createLogger(config.logger) : config.logger; const app = express(); app.disable("x-powered-by"); const errorHandler = config.errorHandler || defaultResultHandler; const compressor = config.server.compression ? compression({ ...(typeof config.server.compression === "object" ? config.server.compression : {}), }) : undefined; const jsonParser = config.server.jsonParser || json(); const multipartParser = config.server.upload ? fileUpload({ ...(typeof config.server.upload === "object" ? config.server.upload : {}), abortOnLimit: false, parseNested: true, }) : undefined; const middlewares = ([] as RequestHandler[]) .concat(compressor || []) .concat(jsonParser) .concat(multipartParser || []); app.use(middlewares); app.use(createParserFailureHandler(errorHandler, logger)); initRouting({ app, routing, logger, config }); app.use(createNotFoundHandler(errorHandler, logger)); const httpServer = app.listen(config.server.listen, () => { logger.info(`Listening ${config.server.listen}`); }); let httpsServer: https.Server | undefined; if (config.https) { httpsServer = https .createServer(config.https.options, app) .listen(config.https.listen, () => { logger.info(`Listening ${config.https!.listen}`); }); } return { app, httpServer, httpsServer, logger }; }