@tsed/platform-express
Version:
A TypeScript Framework on top of Express
200 lines (199 loc) • 7.03 kB
JavaScript
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
}
]);