UNPKG

@tsed/platform-express

Version:
200 lines (199 loc) • 7.03 kB
import "@tsed/platform-multer/express"; import { readFileSync } from "node:fs"; import { dirname, join } from "node:path"; import { fileURLToPath } from "node:url"; import { catchAsyncError, Env, isFunction } from "@tsed/core"; import { constant, inject, logger, runInContext } from "@tsed/di"; import { PlatformExceptions } from "@tsed/platform-exceptions"; import { adapter, application, createContext, PlatformAdapter, PlatformBuilder, PlatformHandler, PlatformResponse } from "@tsed/platform-http"; import { PlatformHandlerType } from "@tsed/platform-router"; import Express from "express"; import { staticsMiddleware } from "../middlewares/staticsMiddleware.js"; import { PlatformExpressHandler } from "../services/PlatformExpressHandler.js"; import { PlatformExpressResponse } from "../services/PlatformExpressResponse.js"; import { convertPath } from "../utils/convertPath.js"; function callNext(next, metadata, $ctx) { if (metadata.type !== PlatformHandlerType.RESPONSE_FN) { return next && $ctx.error && !$ctx.isDone() ? next($ctx.error) : next(); } } function getVersion() { try { const { version } = JSON.parse(readFileSync(join(dirname(fileURLToPath(import.meta.resolve("express"))), "package.json"), "utf8")); return `v${version.split(".")[0]}`; } catch (er) { return "v4"; } } /** * @platform * @express */ export class PlatformExpress extends PlatformAdapter { constructor() { super(...arguments); this.NAME = "express"; } /** * Create new serverless application. In this mode, the component scan are disabled. * @param module * @param settings */ static create(module, settings = {}) { return PlatformBuilder.create(module, { ...settings, adapter: PlatformExpress }); } /** * Bootstrap a server application * @param module * @param settings */ static bootstrap(module, settings = {}) { return PlatformBuilder.bootstrap(module, { ...settings, adapter: PlatformExpress }); } async beforeLoadRoutes() { const { app } = this; // disable x-powered-by header constant("env") === Env.PROD && app.getApp().disable("x-powered-by"); await this.configureViewsEngine(); } afterLoadRoutes() { const { app } = this; const platformExceptions = inject(PlatformExceptions); // NOT FOUND app.use((req, res, next) => { const { $ctx } = req; !$ctx.isDone() && platformExceptions?.resourceNotFound(req.$ctx); }); // EXCEPTION FILTERS app.use((err, req, res, next) => { const { $ctx } = req; !$ctx.isDone() && platformExceptions?.catch(err, $ctx); }); return Promise.resolve(); } mapLayers(layers) { const rawApp = this.app.getApp(); const version = getVersion(); layers.forEach((layer) => { const handlers = layer.getArgs(false); const { path, wildcard } = convertPath(layer.path, version); layer.path = path; if (layer.method === "statics") { rawApp.use(path, this.statics(path, layer.opts)); return; } if (wildcard) { handlers.unshift(((req, _, next) => { if (req.params["0"] && !req.params[wildcard]) { req.params[wildcard] = req.params["0"]; } next(); })); } rawApp[layer.method](path, ...handlers); }); } mapHandler(handler, metadata) { if (metadata.type == PlatformHandlerType.ERR_MIDDLEWARE) { return (error, req, res, next) => { return runInContext(req.$ctx, async () => { const { $ctx } = req; $ctx.next = next; $ctx.error = error; $ctx.error = await catchAsyncError(() => handler($ctx)); return callNext(next, metadata, $ctx); }); }; } return (req, res, next) => { return runInContext(req.$ctx, async () => { const { $ctx } = req; $ctx.next = next; $ctx.error = await catchAsyncError(() => handler($ctx)); return callNext(next, metadata, $ctx); }); }; } useContext() { const invoke = createContext(); const app = application(); app.use(async (request, response, next) => { const $ctx = invoke({ request, response }); await $ctx.start(); $ctx.response.getRes().on("finish", () => $ctx.finish()); return runInContext($ctx, next); }); } createApp() { const app = constant("express.app") || Express(); return { app, callback: () => app }; } statics(endpoint, options) { const { root, ...props } = options; return staticsMiddleware(root, props); } bodyParser(type, additionalOptions = {}) { const opts = constant(`express.bodyParser.${type}`); let parser = Express[type]; let options = {}; if (isFunction(opts)) { parser = opts; options = {}; } if (type === "urlencoded") { options.extended = true; } options.verify = (req, _res, buffer) => { const rawBody = constant(`rawBody`); if (rawBody) { req.rawBody = buffer; } return true; }; return parser({ ...options, ...additionalOptions }); } async configureViewsEngine() { const { app } = this; try { const { exists, disabled } = constant("views") || {}; if (exists && !disabled) { const { PlatformViews } = await import("@tsed/platform-views"); const platformViews = inject(PlatformViews); const express = app.getApp(); platformViews.getEngines().forEach(({ extension, engine }) => { express.engine(extension, engine.render); }); platformViews.viewEngine && express.set("view engine", platformViews.viewEngine); platformViews.root && express.set("views", platformViews.root); } } catch (error) { // istanbul ignore next logger().warn({ event: "PLATFORM_VIEWS_ERROR", message: "Unable to configure the PlatformViews service on your environment.", error }); } } } adapter(PlatformExpress, [ { token: PlatformHandler, useClass: PlatformExpressHandler }, { token: PlatformResponse, useClass: PlatformExpressResponse } ]);