FORMA

提供者与服务

Service 是最常见的 Provider,封装业务逻辑。见 module

提供者(Providers)是 NestJS 依赖注入体系的核心。服务(Service)是一种最常见的提供者,通常使用 @Injectable() 装饰器。除了服务类,NestJS 还支持多种自定义提供者方式,满足不同场景的注入需求。

一、服务(Service)

服务用于封装业务逻辑,如数据验证、数据库操作、调用外部 API 等。控制器负责处理请求,服务负责执行业务,保持关注点分离。

1. 使用 @Injectable() 装饰器

@Injectable() 标记一个类可以被 NestJS 的 IoC 容器管理,并注入到其他类中。

ts
// users.service.ts
import { Injectable } from "@nestjs/common";

@Injectable()
export class UsersService {
  private users = [{ id: 1, name: "John" }];

  findOne(id: number) {
    return this.users.find((user) => user.id === id);
  }

  create(user: { name: string }) {
    const newUser = { id: Date.now(), ...user };
    this.users.push(newUser);
    return newUser;
  }
}

2. 在控制器中注入服务

通过构造函数注入,NestJS 会自动实例化并提供 UsersService

ts
// users.controller.ts
import { Controller, Get, Param, Post, Body } from "@nestjs/common";
import { UsersService } from "./users.service";

@Controller("users")
export class UsersController {
  constructor(private readonly usersService: UsersService) {}

  @Get(":id")
  findOne(@Param("id") id: number) {
    return this.usersService.findOne(id);
  }

  @Post()
  create(@Body() body: { name: string }) {
    return this.usersService.create(body);
  }
}

3. 服务之间的相互依赖

服务也可以注入其他服务,形成清晰的层级。只需在构造函数中声明即可。

ts
@Injectable()
export class OrderService {
  constructor(private readonly usersService: UsersService) {}

  getOrderWithUser(orderId: number) {
    const order = this.findOrder(orderId);
    const user = this.usersService.findOne(order.userId);
    return { order, user };
  }
}

二、自定义提供者

除了类提供者(默认的 @Injectable() 类),NestJS 允许使用多种提供者定义方式,通过 @Module 装饰器的 providers 数组配置。

提供者定义的基本结构包含 provide 令牌(token)和具体的提供者策略。

1. 值提供者(useValue

用于注入常量、第三方库实例或配置对象。

ts
// config.module.ts
import { Module } from "@nestjs/common";

const config = { apiUrl: "https://api.example.com", timeout: 5000 };

@Module({
  providers: [{ provide: "CONFIG", useValue: config }],
})
export class ConfigModule {}

在其他类中通过 @Inject('CONFIG') 注入:

ts
@Injectable()
export class ApiService {
  constructor(@Inject("CONFIG") private config: { apiUrl: string; timeout: number }) {}
}

2. 类提供者(useClass

允许指定某个令牌实际使用的实现类。这在需要替换实现(如测试 mock、多环境适配)时非常有用。

ts
// 定义抽象类或接口
export interface Mailer {
  send(to: string, subject: string, body: string): void;
}

export class SmtpMailer implements Mailer {
  /* ... */
}
export class MockMailer implements Mailer {
  /* ... */
}

// 模块中根据环境选择实现
@Module({
  providers: [
    {
      provide: "MAILER",
      useClass: process.env.NODE_ENV === "production" ? SmtpMailer : MockMailer,
    },
  ],
})
export class MailModule {}

3. 工厂提供者(useFactory

动态创建实例,支持依赖其他提供者。工厂可以是异步的,返回 Promise

ts
@Module({
  providers: [
    {
      provide: "DATABASE_CONNECTION",
      useFactory: async (configService: ConfigService) => {
        const dbConfig = configService.getDatabaseConfig();
        const connection = await createDatabaseConnection(dbConfig);
        return connection;
      },
      inject: [ConfigService], // 注入依赖,数组中顺序对应工厂函数参数
    },
  ],
})
export class DatabaseModule {}

使用工厂时,inject 数组用于声明需要哪些提供者作为工厂参数。

4. 别名提供者(useExisting

为已有的提供者创建一个别名,使得可以通过不同令牌注入同一个实例。

ts
@Module({
  providers: [SomeService, { provide: "ALIAS_SERVICE", useExisting: SomeService }],
})
export class MyModule {}

此时,'ALIAS_SERVICE'SomeService 令牌指向同一个实例。

三、异步提供者

如果提供者的初始化需要异步操作(如数据库连接、HTTP 请求),可以使用 async 工厂函数或返回 Promise 的值提供者。

NestJS 会等待异步提供者解析完成,才注入到依赖该提供者的其他类中。

ts
@Module({
  providers: [
    {
      provide: "ASYNC_DB",
      useFactory: async () => {
        const connection = await orm.createConnection();
        return connection;
      },
    },
  ],
})
export class AppModule {}

四、可选提供者(@Optional()

当某个依赖不是必须存在时,可以使用 @Optional() 装饰器。如果该提供者未注册,注入的值会是 undefined,而不会抛出错误。

ts
@Injectable()
export class LoggerService {
  constructor(@Optional() private readonly config?: ConfigService) {
    if (config) {
      // 使用 config 配置日志级别
    } else {
      // 使用默认配置
    }
  }
}

五、提供者的作用域

提供者默认是单例(SINGLETON)作用域,即整个应用共享同一个实例。但也可以通过 @Injectable({ scope: Scope.REQUEST })Scope.TRANSIENT 改变作用域。详细内容已在“模块系统与依赖注入”中介绍。

六、自定义提供者的令牌类型

提供者的 provide 令牌可以是:

  • 字符串或 Symbol(如上例 'CONFIG'
  • 类本身(默认情况,useClass 或简写时)
  • 抽象类或接口(但在 JavaScript 中无法直接使用接口,需要借助字符串或 Symbol)

为了获得更好的类型安全,推荐使用自定义符号或类作为令牌。

ts
export const DATABASE_CONNECTION = Symbol('DATABASE_CONNECTION');

// 在 provider 中使用
{ provide: DATABASE_CONNECTION, useFactory: ... }

// 注入时
@Inject(DATABASE_CONNECTION) private connection: Connection

总结

提供者类型使用方式适用场景
类提供者(服务)默认 @Injectable(),在 providers 中直接添加类专注业务逻辑、可复用的服务
值提供者useValue常量、配置对象、第三方库实例
类提供者(替换)useClass接口的不同实现(mock、生产环境)
工厂提供者useFactory + inject需要依赖其他提供者创建实例
别名提供者useExisting同一实例不同令牌访问
异步提供者useFactory 返回 Promise数据库连接、初始化外部资源
可选提供者@Optional() 装饰器依赖非必需的情况

理解这些提供者形式,可以灵活管理应用中的各种依赖,让 NestJS 应用更加模块化和可测试。

参考文献

以下链接在编写时均可正常访问:

资料说明
NestJS 文档官方
Providers本章主题
Request lifecycle执行顺序

Series

new

18 / 19