一、控制器(Controller)
控制器负责处理传入的请求 并向客户端返回响应。
接收客户的请求,然后告诉 服务层该做什么,最后把结果端给客户。
对应请求路径的配置。
控制器不过多介绍,比较好理解,对应的规则可以查阅官方文档。
二、提供者(Provide)
提供者是Nest中的一个基本概念。
许多基本的Nest类可以被视为提供者 - 服务、存储库、工厂、助手等。提供者的主要思想是它可以作为依赖项注入;
这意味着对象可以彼此之间建立各种关系,而对象实例的"连接"功能可以在很大程度上委托给Nest运行时系统。
1. 它是什么
提供者是普通的JavaScript类,在模块中声明为providers。
Provider 是一个 可以被依赖注入系统 "提供"给其他类(通常是控制器 Controller 或其他服务 Service)的对象、值或函数。
当你在一个类(如 Controller)的构造函数中声明了一个依赖项时,NestJS 会去查找对应的 Provider 来注入这个依赖。
白话描述一:它是一个对象、值或函数,它可以通过 依赖注入 的方式,注入到系统中供其它类使用;
"Provider 是 NestJS IoC 容器管理的资源(可以是类实例、值或工厂函数),它作为依赖项,被自动注入到需要它的类中。"
2. 使用:
使用提供者通常分为三个步骤:定义 、注册 、注入。
2.1 定义
通常使用 @Injectable() 装饰器标记一个类。
typescript
import { Injectable } from '@nestjs/common';
@Injectable() // 这个装饰器告诉 NestJS,CatService 是一个提供者
export class CatService {
private readonly cats = [];
create(cat: any) {
this.cats.push(cat);
}
findAll(): any[] {
return this.cats;
}
}
2.2 注册
必须在模块(Module)的 providers 数组中注册该提供者,这样它才能被该模块内的其他类发现。
typescript
// cats.module.ts
import { Module } from '@nestjs/common';
import { CatsController } from './cats.controller';
import { CatService } from './cat.service';
@Module({
controllers: [CatsController],
providers: [CatService], // 在这里注册
})
export class CatsModule {}
2.3 注入
在构造函数中声明依赖,NestJS 会自动解析类型并注入实例。
typescript
// cats.controller.ts
import { Controller, Get, Post, Body } from '@nestjs/common';
import { CatService } from './cat.service';
@Controller('cats')
export class CatsController {
// 通过构造函数注入
constructor(
private readonly catService: CatService
) {}
@Post()
create(@Body() createCatDto: any) {
this.catService.create(createCatDto);
return 'Action has been executed';
}
@Get()
findAll() {
return this.catService.findAll();
}
}
2.4 自定义提供者
除了标准的类提供者,NestJS 还支持更灵活的自定义提供者,用于处理复杂的场景。
定义、注册过程
less
const configServiceProvider = {
provide: ConfigService,
useClass:
process.env.NODE_ENV === 'development'
? DevelopmentConfigService
: ProductionConfigService,
};
@Injectable()
class LoggerService {
/* implementation details */
}
@Module({
providers: [
// 一、普通提供者,或称为:类提供者
configServiceProvider,
// 二、值提供者示例
{
provide: 'AA',
useValue: {
a: 1,
b: 2,
},
},
// 三、工厂提供者示例:动态创建数据库配置
{
provide: 'DB_CONFIG',
useFactory: (usersService: UsersService) => {
// 工厂函数可以执行异步操作、条件判断等
const env = process.env.NODE_ENV || 'development';
return {
host: env === 'production' ? 'prod.db.com' : 'localhost',
port: 5432,
database: 'users_db',
timestamp: new Date().toISOString(),
// 可以使用注入的依赖
serviceName: usersService.constructor.name,
};
},
inject: [UsersService], // 声明依赖项
},
// 四、异步工厂提供者示例
{
provide: 'ASYNC_CONNECTION',
useFactory: async () => {
// 模拟异步操作,如数据库连接
await new Promise(resolve => setTimeout(resolve, 8000));
return {
status: 'connected',
connectionId: Math.random().toString(36).substring(7),
connectedAt: new Date().toISOString(),
};
},
},
// 别名提供者: useExisting
LoggerService,
{
provide: 'AliasedLoggerService',
useExisting: LoggerService,
},
],
})
注入方式:
less
@Controller('users')
export class UsersController {
constructor(
@Inject('AA') private readonly aa: any,
@Inject('DB_CONFIG') private readonly dbConfig: any,
@Inject('ASYNC_CONNECTION') private readonly connection: any,
) {
// 在构造函数中可以看到工厂提供者创建的实例
console.log('数据库配置:', this.dbConfig);
console.log('异步连接:', this.connection);
}
}
三、模块(module)
在 NestJS 框架中,@Module() 装饰器是构建应用程序架构的基石。NestJS 的整个应用结构就是由一个个模块组成的树状结构。
以下是对 @Module() 的详细介绍,包括它的定义、作用、核心属性以及使用示例。
1. 它是什么
@Module() 是一个 TypeScript 装饰器(Decorator),用于定义一个 NestJS 模块。
在 NestJS 中,模块是组织代码的基本单元。每个 NestJS 应用程序至少有一个模块(通常是 AppModule),作为应用程序的根模块。通过模块,NestJS 能够利用依赖注入(Dependency Injection)系统来管理类之间的依赖关系。
2. 它的作用是什么?
@Module() 的主要作用是定义上下文边界 和组织依赖关系。它告诉 NestJS 编译器:
- 这个模块包含哪些提供者(Services/Providers)。
- 这个模块向外暴露哪些提供者供其他模块使用。
- 这个模块需要导入哪些其他模块。
- 这个模块包含哪些控制器(Controllers)。
简单来说,它解决了代码的高内聚、低耦合问题,让大型项目的结构清晰可见。
3. 它的核心属性
@Module() 装饰器接收一个配置对象,该对象包含四个主要属性:
| 属性 | 类型 | 描述 |
|---|---|---|
providers |
Provider[] |
提供者数组。这里定义的服务(Service)、仓库(Repository)或其他可注入的类,将在当前模块内通过依赖注入可用。 |
exports |
Provider[] |
导出数组 。默认情况下,模块内的提供者只能在模块内部使用。如果想让其他模块也能使用这些提供者,必须在这里列出它们。 |
imports |
Module[] |
导入数组。列出当前模块所依赖的其他模块。导入后,当前模块可以使用那些被导出模块中的提供者。 |
controllers |
Controller[] |
控制器数组。定义属于该模块的控制器,用于处理 HTTP 请求并返回响应。 |
4. 使用
假设我们要构建一个简单的用户管理系统,包含 UsersModule(用户模块)和 DatabaseModule(数据库模块)。
场景 A:定义一个基础模块 (UsersModule)
typescript
// users/users.service.ts
import { Injectable } from '@nestjs/common';
@Injectable()
export class UsersService {
findAll() {
return [{ id: 1, name: 'Alice' }, { id: 2, name: 'Bob' }];
}
}
// users/users.controller.ts
import { Controller, Get } from '@nestjs/common';
import { UsersService } from './users.service';
@Controller('users')
export class UsersController {
// 依赖注入 UsersService
constructor(private readonly usersService: UsersService) {}
@Get()
findAll() {
return this.usersService.findAll();
}
}
// users/users.module.ts
import { Module } from '@nestjs/common';
import { UsersController } from './users.controller';
import { UsersService } from './users.service';
@Module({
// 1. 注册控制器,处理 /users 路由
controllers: [UsersController],
// 2. 注册服务,使其可在本模块内注入
providers: [UsersService],
// 3. 如果其他模块想用 UsersService,需在此导出
exports: [UsersService],
// 4. 如果依赖其他模块(如数据库),在此导入
imports: [],
})
export class UsersModule {}
场景 B:模块间的依赖 (导入与导出)
假设我们有一个 DatabaseModule 提供了一个 DbConnection 服务,并且我们希望 UsersModule 能使用它。
typescript
// database/database.service.ts
import { Injectable } from '@nestjs/common';
@Injectable()
export class DbConnection {
connect() {
console.log('Connected to DB');
}
}
// database/database.module.ts
import { Module } from '@nestjs/common';
import { DbConnection } from './database.service';
@Module({
providers: [DbConnection],
// 关键点:必须导出,其他模块才能用
exports: [DbConnection],
})
export class DatabaseModule {}
现在,修改 UsersModule 来使用它:
typescript
// users/users.module.ts
import { Module } from '@nestjs/common';
import { UsersController } from './users.controller';
import { UsersService } from './users.service';
import { DatabaseModule } from '../database/database.module'; // 引入模块定义
@Module({
controllers: [UsersController],
providers: [UsersService],
exports: [UsersService],
// 关键点:导入 DatabaseModule,这样 UsersService 就可以注入 DbConnection 了
imports: [DatabaseModule],
})
export class UsersModule {}
users.service.ts
typescript
// users/users.service.ts (更新后)
import { Injectable } from '@nestjs/common';
import { DbConnection } from '../database/database.service';
@Injectable()
export class UsersService {
// 现在可以注入了,因为 UsersModule 导入了 DatabaseModule 且后者导出了 DbConnection
constructor(private readonly db: DbConnection) {
this.db.connect();
}
findAll() {
return [{ id: 1, name: 'Alice' }];
}
}
5. 最佳实践与注意事项
- 根模块 (Root Module) :每个应用都有一个根模块(通常在
app.module.ts),它是模块树的入口。NestJS 从根模块开始解析依赖图。 - 不要循环依赖 :尽量避免模块 A 导入模块 B,同时模块 B 又导入模块 A。如果必须这样做,需要使用
forwardRef(() => ModuleB)。 - 按需导出:只导出其他模块真正需要的提供者。这有助于隐藏内部实现细节,保持封装性。
- 功能内聚:一个模块应该专注于单一的业务领域(例如:用户模块只处理用户相关逻辑,订单模块只处理订单逻辑)。
- 全局模块 :如果一个模块(如配置模块、日志模块)需要在几乎所有地方使用,可以使用
@Global()装饰器将其标记为全局模块,这样就不需要在每个模块的imports数组中重复导入它。
6. 总结
@Module() 是 NestJS 的骨架。
- Controllers 处理请求。
- Providers 处理业务逻辑。
- Imports/Exports 处理模块间的通信。
- @Module() 将它们捆绑在一起,形成一个有机的整体。