使用 Express.js 创建简版 Nest.js
date
Mar 16, 2022
slug
use-express-create-mini-nest
status
Published
tags
Nodejs
summary
使用 Typescript + 装饰器创建 Nest.js 中“注入”的行为来编写代码🥴
type
Post
express 用了一段时间,最近看了看nestjs还有ts入门实战,结合自己使用ts+express的经验,对expressjs如何向nestjs演化做一个总结
规划&思路
主要是包装器、中间件的结合使用。对于路由(controller)的定义采用nestjs中注入的方式实现
装饰器
nestjs中处处可见,那么这个东西到底是什么?详情见
这里做一些总结与注意事项:
- 装饰器本质就是一个函数,装饰器通过@来使用
- 装饰器收集顺序:由上到下,由左向右;
- 装饰器执行顺序:由下到上,由右向左
- 类的装饰器:
- 接受参数:constrouctor
- 执行时机:在类定义后立即执行,不论声明几个实例都只执行一次
- 函数的装饰器:
- 接受参数:
- target 普通方法对应的是类的 prototype, 静态方法对应的是类的构造函数
- key 方法名
- descriptor 类比 Object.defineProperty 的 descriptor
- 执行时机:在函数定义后立即执行
- 参数的装饰器:
- 接受参数:
- target 普通方法对应的是类的 prototype, 静态方法对应的是类的构造函数
- key 参数名
- paramIndex 参数索引
- 执行时机: 在参数定义后立即执行
- 属性装饰器:
- 接受参数:
- target 普通方法对应的是类的 prototype, 静态方法对应的是类的构造函数
- key 属性名
- 执行时机:在属性定义后立即执行
- 需要注意的是:因为属性的装饰器函数没有 descriptor 参数,如果要强行改则需自己定义 descriptor 并返回
使用元数据进行依赖收集
import { log } from "../decorator"
@controller("/api")
export default class LoginController {
@post("/login")
@middleware(log)
login() {
// todo
}
}
这样子的写法在nestjs、eggjs中很常见,那么如何用ts来自己实现呢?
// controller.ts 收集定义的装饰器(即定义的元数据)
import { RequestHandler } from "express";
import "reflect-metadata";
import { Router } from "express";
const router = Router();
enum HttpMethods {
get = "get",
post = "post"
};
export function controller(root: string) {
// new (...args: any[]) => any 是ts中用来定义构造器的类型写法
return function (target: new (...args: any[]) => any) {
// 详情见readme,为什么不用for in或object.keys见 https://developer.mozilla.org/zh-CN/docs/Web/JavaScript/Enumerability_and_ownership_of_properties
Reflect.ownKeys(target.prototype).forEach((key) => {
// 收集定义的方法装饰器、中间件
const path: string = Reflect.getMetadata("path", target.prototype, key);
const method: HttpMethods = Reflect.getMetadata(
"method",
target.prototype,
key
);
const handler = target.prototype[key];
const middleware: RequestHandler[] = Reflect.getMetadata(
"middleware",
target.prototype,
key
);
const fullPath = root === "/" ? path : `${root}${path}`;
if (fullPath && Reflect.has(router, method) && handler) {
if (middleware) {
router[method](fullPath, ...middleware, handler);
} else {
router[method](fullPath, handler);
}
}
});
};
}
定义元数据(get/post、midderware)
// request.ts
import "reflect-metadata";
enum HttpMethods {
get = "get",
post = "post"
}
function requestDecoratorFactory(method: HttpMethods) {
return function (path: string) {
return function (target: any, key: string) {
// 定义元数据
Reflect.defineMetadata("path", path, target, key);
Reflect.defineMetadata("method", method, target, key);
};
};
}
export const get = requestDecoratorFactory(HttpMethods.get);
export const post = requestDecoratorFactory(HttpMethods.post);
下面中间件装饰器定义需要注意的是中间件装饰器的执行顺序,要保证由下到上,而不是由上到下。(因为express中定义路由时第二个参数可以传入固定顺序的中间件数组,为了改变顺序我们不能穿数组)
// middleware.ts
import { RequestHandler } from "express";
import "reflect-metadata";
// export function middleware(middleware: RequestHandler | RequestHandler[]) { // 直接传入数组的方式,中间件顺序与数组索引顺序一致
export function middleware(middleware: RequestHandler) {
return function (target: any, key: string) {
// 这种写法会保证中间件执行顺序是由下到上的正常顺序
const prevMiddleware: RequestHandler[] =
Reflect.getMetadata("middleware", target, key) || [];
const middlewares: RequestHandler[] = [...prevMiddleware, middleware];
Reflect.defineMetadata("middleware", middlewares, target, key);
};
}
具体代码可以查看