import * as express from 'express'; import * as cors from 'cors'; import * as cookieParser from 'cookie-parser'; import * as bodyParser from 'body-parser'; import { R, log, constants, fsPath } from '../libs'; const listEndpoints = require('express-list-endpoints'); export const Router = express.Router; export { staticRoutes } from './middleware/staticRoutes'; export type Request = express.Request; export type Response = express.Response; const IS_PRODUCTION = process.env.NODE_ENV === 'production'; interface IErrorStack { message: string; stack: string[]; } /** * Turns an Error stack into a friendlier JSON value. */ function formatErrorStack(stack: string = ''): IErrorStack { const lines = stack.split('\n'); const message = lines[0]; lines.shift(); return { message, stack: lines.map(line => line.trim()), }; } /** * Sends an HTTP error to the client, with full stack details * if running locally in development. */ export function sendError(code: number, res: express.Response, err: Error) { const error = IS_PRODUCTION ? err.message : formatErrorStack(err.stack); res.status(code).send({ status: code, error, }); } /** * Create an express app. */ export function app( options: { cors?: cors.CorsOptions; json?: bodyParser.OptionsJson; static?: string; } = {}, ) { const app = express() .use(cors(options.cors)) .use(bodyParser.json(options.json) as any) // HACK: avoid type incompatibility. .use(cookieParser()); if (options.static) { app.use(express.static(options.static)); } return app; } /** * Creates an express router. * NB: * This is a helper that makes it easier to create * and export a router without the annoying typescript * errors complaining about the `express-serve-static-core` * reference requirement. */ export function router() { const routes = express.Router(); return routes as express.Router; } /** * Retrieves a list of all routes that have been registered. */ export function routes(router: express.Router): RouteInfo[] { const items = listEndpoints(router) as RouteInfo[]; return items.reduce( (acc, next) => { const index = acc.findIndex(r => r.path === next.path); if (index > -1) { // Route already exist, merge in the methods. const route = items[index]; route.methods = R.uniq([...route.methods, ...next.methods]); return acc; } return [...acc, next]; }, [] as RouteInfo[], ); } export type RouteInfo = { path: string; methods: Array<'GET' | 'POST' | 'PUT' | 'DELETE' | 'PATCH'>; }; /** * Standard logger for when the express application starts. */ export function logStarted(port: number, args: {} = {}) { const PACKAGE = require(fsPath.resolve('./package.json')); log.info(`\n> Ready on ${log.cyan('localhost')}:${log.magenta(port)}`); log.info(); log.info.gray(` name: ${log.white(PACKAGE.name)}@${PACKAGE.version}`); log.info.gray(` dev: ${constants.IS_DEV}`); log.info(); }