使用基于路由和 DI 编写的装饰器 Node.js 和 Express 应用
在构建现代 Node.js 和 Express 应用程序时,管理路由和依赖注入会变得越来越复杂。处理控制器、服务和中间件时如果没有清晰的分离,通常会导致代码更难维护。但是,使用正确的工具和设计模式,我们可以大大简化这个过程。
在本文中,我将引导您了解如何使用@lemondi 库构建具有**基于装饰器的路由**和**依赖注入**的 Node.js 应用程序。
为什么要使用装饰器和依赖注入?
装饰器是 TypeScript 和 JavaScript 中的一项强大功能,可让您向类、方法和属性添加元数据。借助装饰器,我们可以注释路由方法、定义其 HTTP 方法并处理依赖项注入,而无需编写复杂的样板代码。
依赖注入 (DI) 有助于管理服务实例化和注入其他组件的方式。它将组件彼此分离,使您的应用程序更加模块化且更易于测试。在我们的例子中,我们将使用 DI 来实现数据库连接和路由等服务。
**@lemondi** 库通过自动化 DI、处理装饰器和减少对样板代码的需求来简化流程。让我们深入了解它的工作原理!
1. 项目设置
在我们开始构建之前,让我们确保已经安装了所需的库:
npm init -y npm install express reflect-metadata @lemondi/core @lemondi/scanner typeorm sqlite3 class-transformer npm install --save-dev typescript @types/node @types/express
接下来,让我们通过创建 `tsconfig.json` 文件来设置 TypeScript:
{
"compilerOptions": {
"lib": ["es5", "es6", "dom"],
"target": "es5",
"module": "commonjs",
"moduleResolution": "node",
"outDir": "./dist",
"experimentalDecorators": true,
"emitDecoratorMetadata": true
}
}此配置启用了 TypeScript 对装饰器和元数据反射的支持,这是 `@lemondi` 库所必需的。
2. 路由和注入的装饰器
routing.ts - 创建装饰器
首先,让我们创建两个装饰器:一个用于类(`@Router`)来定义路由器,另一个用于方法(`@Route`)来定义 HTTP 路由。
// file: src/decorators/routing.ts
import { createClassDecorator, createMethodDecorator } from "@lemondi/scanner";
// Enum for HTTP methods
export enum HttpMethod {
GET = "get",
POST = "post",
PUT = "put",
DELETE = "delete",
PATCH = "patch",
OPTIONS = "options",
}
// @Router decorator for class routing
export const Router = createClassDecorator<{ path: string }>("Router");
// @Route decorator for method routing
export const Route = createMethodDecorator<{ path: string; method: HttpMethod }>("Route");这里我们使用 `@lemondi/scanner` 的 `createClassDecorator` 和 `createMethodDecorator` 来简化路由装饰器的创建。
3.定义数据源
datasource.ts - 数据源工厂
我们需要一种方法来创建和注入“数据源”(例如,用于连接到数据库)。这就是“@lemondi”库的“@Factory”和“@Instantiate”装饰器发挥作用的地方。
// file: src/factories/datasource.ts
import { Factory, FilesLoader, Instantiate } from "@lemondi/core";
import { DataSource } from "typeorm";
@Factory()
export class DataSourceFactory {
@Instantiate({ qualifiers: [DataSource] })
async createDatasource() {
const ds = new DataSource({
type: "sqlite",
database: ":memory:",
synchronize: true,
entities: [FilesLoader.buildPath(__dirname, "..", "models", "*.entity.{js,ts}")],
});
await ds.initialize();
return ds;
}
}`@Factory` 装饰器将 `DataSourceFactory` 标记为组件的提供者,而 `@Instantiate` 将 `createDatasource` 方法标记为 `DataSource` 组件的提供者。DI 将自动解析并注入所需的 `DataSource`。
4. 定义实体
user.entity.ts - TypeORM 实体
这是一个简单的 TypeORM 实体,用于定义“用户”模型。
// file: src/models/user.entity.ts
import { Column, Entity, PrimaryGeneratedColumn } from "typeorm";
import { plainToClass } from "class-transformer";
@Entity({ name: "users" })
export class User {
@PrimaryGeneratedColumn("uuid")
id?: string;
@Column()
firstName: string;
@Column()
lastName: string;
static fromJson(json: User) {
return plainToClass(User, json);
}
}此实体表示数据库中具有字段“firstName”和“lastName”的“User”。我们还提供了一个实用函数“fromJson”,可轻松将 JSON 数据转换为“User”类的实例。
5.创建路由器
UsersRouter.ts - 定义路由
有了装饰器,我们现在可以定义“UsersRouter”类来处理与用户相关的路由。
// file: src/routers/UsersRouter.ts
import { HttpMethod, Route, Router } from "../decorators/routing";
import { UsersService } from "../services/UsersService";
import { Request } from "express";
import { User } from "../models/user.entity";
@Router({ path: "/users" })
export class UsersRouter {
constructor(private readonly usersService: UsersService) {}
@Route({ path: "/", method: HttpMethod.GET })
getUsers() {
return this.usersService.find();
}
@Route({ path: "/", method: HttpMethod.POST })
createUser(req: Request) {
const data = User.fromJson(req.body);
return this.usersService.save(data);
}
}这里,`@Router` 装饰器定义基本路径 `/users`,`@Route` 装饰器处理用于检索和创建用户的 GET 和 POST 方法。
6.服务层
UsersService.ts - 处理业务逻辑
我们定义与数据库交互的服务。
// file: src/services/UsersService.ts
import { Component } from "@lemondi/core";
import { DataSource, Repository } from "typeorm";
import { User } from "../models/user.entity";
@Component()
export class UsersService {
private repository: Repository;
constructor(dataSource: DataSource) {
this.repository = dataSource.getRepository(User);
}
save(user: User) {
return this.repository.save(user);
}
find() {
return this.repository.find();
}
} `UsersService` 类用 `@Component()` 修饰,其构造函数会自动注入 `DataSource` 实例。这样一来,服务无需任何手动实例化即可执行数据库操作。
7. 引导应用程序
app.ts——整合所有内容
最后,我们使用 `@lemondi` DI 系统初始化应用程序并动态绑定路由。
// file: src/app.ts
import "reflect-metadata";
import { Component, FilesLoader, instantiate, OnInit, start } from "@lemondi/core";
import * as express from "express";
import { findClassDecorators, findMethodDecorators, scan } from "@lemondi/scanner";
import { Route, Router } from "./decorators/routing";
@Component()
class App {
@OnInit()
async onStart() {
const server = express();
server.use(express.json());
const routers = scan(Router);
for (const router of routers) {
const routerInstance = await instantiate(router);
const [routerDecorator] = findClassDecorators(router, Router);
for (const prop of Reflect.ownKeys(router.prototype)) {
const [props] = findMethodDecorators(router, prop, Route);
if (props) {
const url = routerDecorator.decoratorProps.path + props.decoratorProps.path;
server[props.decoratorProps.method](url, async (...args) => {
const result = await Promise.resolve(routerInstance[prop].call(routerInstance, ...args));
args[1].json(result).end();
});
}
}
}
server.listen(3000);
}
}
start({
importFiles: [
FilesLoader.buildPath(__dirname, "factories", "**", "*.js"),
FilesLoader.buildPath(__dirname, "routers", "**", "*.js"),
],
modules: [App],
});在这里,我们使用 `@OnInit` 装饰器在应用程序实例化后初始化 Express 服务器。我们动态扫描 `@Router` 和 `@Route` 装饰器并在服务器上配置路由。
您现在可以使用以下命令运行该应用程序:
tsc && node ./dist/app.js
结论
使用“@lemondi”提供的装饰器和 DI 系统,我们简化了 Node.js 和 Express 应用程序。这种方法抽象了路由和依赖管理通常所需的大量样板代码,从而产生了更简洁、更易于维护的代码。
如果你厌倦了手动配置路由和服务,这种模式绝对值得探索。通过使用装饰器,我们可以让代码更具声明性、可读性和模块化。