使用基于路由和 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 应用程序。这种方法抽象了路由和依赖管理通常所需的大量样板代码,从而产生了更简洁、更易于维护的代码。
如果你厌倦了手动配置路由和服务,这种模式绝对值得探索。通过使用装饰器,我们可以让代码更具声明性、可读性和模块化。