提供者 Provider
本教程共 47 篇 · 第 7 篇 · 更新于 2026-08-09 · 约 11 分钟阅读
本节目标:理解 Provider 的本质,学会创建服务、注册提供者、使用自定义 Provider 的各种姿势。
Provider 是什么
Provider 是 NestJS 里”干活”的角色。
控制器负责接请求,Provider 负责处理业务逻辑。打个比方:控制器是餐厅的服务员,Provider 就是厨房里的厨师。
Provider 最常见的形态就是 Service(服务)。但不仅仅是 Service——仓库(Repository)、工厂(Factory)、辅助工具类,都可以是 Provider。
核心原则:任何能被依赖注入系统管理的类,都是 Provider。
创建一个服务
用 @Injectable() 装饰器标记一个类为 Provider:
import { Injectable } from '@nestjs/common';
@Injectable()
export class CatsService {
private readonly cats: Cat[] = [];
create(cat: Cat) {
this.cats.push(cat);
}
findAll(): Cat[] {
return this.cats;
}
findOne(id: number): Cat {
return this.cats.find(cat => cat.id === id);
}
}
@Injectable() 告诉 NestJS:“这个类归你管。” NestJS 的 IoC 容器会负责创建和管理这个类的实例。
Tip用 CLI 创建服务:
nest g service cats。自动加好@Injectable()装饰器。
注册 Provider
Provider 必须在模块里注册,不然 NestJS 不知道它的存在:
@Module({
controllers: [CatsController],
providers: [CatsService],
})
export class CatsModule {}
注册之后,NestJS 就能在需要 CatsService 的地方自动注入它。
标准 Provider 写法
你可能觉得 providers: [CatsService] 这种写法很简单。其实它是简写,完整写法是这样的:
providers: [
{
provide: CatsService,
useClass: CatsService,
},
]
provide 指定 token(令牌),useClass 指定用哪个类来创建实例。简写形式就是当 token 和类名相同时的快捷方式。
理解了完整写法,你就能明白 NestJS 的自定义 Provider 是怎么回事了。
自定义 Provider
当标准写法满足不了需求时,就该用自定义 Provider 了。NestJS 提供了四种自定义方式。
useValue — 值 Provider
直接提供一个值,可以是任何对象:
@Module({
providers: [
{
provide: 'CONFIG_OPTIONS',
useValue: {
apiKey: 'xxx',
timeout: 5000,
},
},
],
})
export class AppModule {}
注入时用 @Inject() 装饰器指定 token:
@Injectable()
export class AppService {
constructor(
@Inject('CONFIG_OPTIONS') private options: ConfigOptions,
) {}
}
useValue 最常见的用途:
- 注入配置常量
- 注入外部库的实例
- 测试时用 mock 对象替换真实服务
// 测试时用 mock 替换真实服务
const mockCatsService = {
findAll: () => [{ id: 1, name: 'Tom' }],
};
@Module({
providers: [
{
provide: CatsService,
useValue: mockCatsService,
},
],
})
export class AppModule {}
useClass — 类 Provider
动态决定用哪个类:
const configServiceProvider = {
provide: ConfigService,
useClass:
process.env.NODE_ENV === 'development'
? DevelopmentConfigService
: ProductionConfigService,
};
@Module({
providers: [configServiceProvider],
})
export class AppModule {}
开发环境用 DevelopmentConfigService,生产环境用 ProductionConfigService。但对外暴露的 token 都是 ConfigService,使用者不需要关心具体实现。
Tip这种”同一个接口、不同实现”的模式,就是面向接口编程的精髓。
useFactory — 工厂 Provider
用工厂函数动态创建 Provider:
@Module({
providers: [
{
provide: 'DATABASE_CONNECTION',
useFactory: (configService: ConfigService) => {
return createConnection({
host: configService.get('DB_HOST'),
port: configService.get('DB_PORT'),
});
},
inject: [ConfigService],
},
],
})
export class AppModule {}
useFactory 的返回值就是 Provider 的值。inject 数组声明工厂函数需要的依赖,NestJS 会自动注入。
工厂函数也可以是异步的:
{
provide: 'ASYNC_CONFIG',
useFactory: async () => {
const config = await loadRemoteConfig();
return config;
},
}
Note
inject数组里的顺序要和工厂函数的参数顺序一一对应。NestJS 按顺序注入。
useExisting — 别名 Provider
给已有的 Provider 创建一个别名:
@Module({
providers: [
LoggerService,
{
provide: 'AliasedLogger',
useExisting: LoggerService,
},
],
})
export class AppModule {}
现在 'AliasedLogger' 和 LoggerService 指向同一个实例。两种 token 都能用。
自定义 Token
前面用的 token 都是类名。但 token 不一定非得是类名——字符串和 Symbol 也行。
字符串 Token
providers: [
{
provide: 'CONNECTION',
useValue: connection,
},
]
// 注入
constructor(@Inject('CONNECTION') connection: Connection) {}
Symbol Token
// constants.ts
export const CONNECTION = Symbol('CONNECTION');
// 注册
providers: [
{
provide: CONNECTION,
useValue: connection,
},
]
// 注入
constructor(@Inject(CONNECTION) connection: Connection) {}
Tip推荐把 token 定义在
constants.ts里,统一管理。Symbol 比字符串更安全——不会和其他模块的 token 撞名。
用常量文件管理 Token
实际项目中,推荐把所有自定义 Token 集中放在一个文件里管理,避免散落各处:
// constants.ts
export const CONFIG_OPTIONS = 'CONFIG_OPTIONS';
export const DATABASE_CONNECTION = 'DATABASE_CONNECTION';
使用时在各处统一导入,保持 Token 名称一致。
接口和抽象类
TypeScript 的 interface 编译后就没了,不能当 token 用。但抽象类可以。
// 用抽象类当 token
export abstract class LoggerService {
abstract log(message: string): void;
}
@Injectable()
export class ConsoleLoggerService extends LoggerService {
log(message: string) {
console.log(message);
}
}
@Module({
providers: [
{
provide: LoggerService, // 抽象类当 token
useClass: ConsoleLoggerService,
},
],
})
export class AppModule {}
注入时直接用抽象类类型,不需要 @Inject():
@Injectable()
export class CatsService {
constructor(private logger: LoggerService) {}
}
Note如果要用 interface,就得搭配 Symbol 或字符串 token,然后用
@Inject()注入。抽象类更优雅——既能当类型约束,又能当 DI token。
Provider 作用域
Provider 默认是单例的。也可以改成其他作用域:
import { Injectable, Scope } from '@nestjs/common';
@Injectable({ scope: Scope.REQUEST })
export class RequestService {
private requestId: string;
}
| 作用域 | 说明 | 性能影响 |
|---|---|---|
DEFAULT(默认) | 单例,全局共享一个实例 | 无 |
REQUEST | 每个请求创建一个新实例 | 中等 |
TRANSIENT | 每次注入都创建新实例 | 较高 |
Tip99% 的场景用默认的单例就够了。
REQUEST作用域适合需要请求级别状态隔离的场景,比如多租户应用。
可选注入
有时候某个依赖不是必须的,用 @Optional() 标记:
@Injectable()
export class HttpService {
constructor(
@Optional()
private readonly logger?: LoggerService,
) {}
request(url: string) {
this.logger?.log(`请求 ${url}`);
}
}
如果 LoggerService 没注册,也不会报错。logger 的值会是 undefined。
导出自定义 Provider
自定义 Provider 默认只在当前模块可用。想让别的模块用,得在 exports 里导出:
const connectionFactory = {
provide: 'CONNECTION',
useFactory: (options: OptionsProvider) => {
return createConnection(options.get());
},
inject: [OptionsProvider],
};
@Module({
providers: [connectionFactory],
exports: ['CONNECTION'], // 用 token 导出
})
export class DatabaseModule {}
也可以直接导出整个 provider 对象:
@Module({
providers: [connectionFactory],
exports: [connectionFactory], // 导出整个对象
})
export class DatabaseModule {}
常见 Provider 示例
配置服务
import { Injectable } from '@nestjs/common';
import { ConfigService } from '@nestjs/config';
@Injectable()
export class AppConfigService {
constructor(private configService: ConfigService) {}
get databaseHost(): string {
return this.configService.get<string>('DATABASE_HOST');
}
get databasePort(): number {
return this.configService.get<number>('DATABASE_PORT', 3306);
}
get jwtSecret(): string {
return this.configService.get<string>('JWT_SECRET');
}
}
用 getter 封装,调用时 appConfigService.databaseHost 而不是 appConfigService.getDatabaseHost()。代码更简洁。
缓存服务
import { Injectable } from '@nestjs/common';
@Injectable()
export class CacheService {
private cache = new Map<string, { value: any; expire: number }>();
set(key: string, value: any, ttl: number = 60000) {
this.cache.set(key, {
value,
expire: Date.now() + ttl,
});
}
get<T>(key: string): T | null {
const item = this.cache.get(key);
if (!item) return null;
if (Date.now() > item.expire) {
this.cache.delete(key);
return null;
}
return item.value as T;
}
delete(key: string) {
this.cache.delete(key);
}
}
小结
Provider 是 NestJS 里真正干活的部分。
核心知识点:
@Injectable()标记一个类为 Provider- Provider 必须在模块的
providers里注册 providers: [CatsService]是{ provide: CatsService, useClass: CatsService }的简写- 四种自定义 Provider:
useValue、useClass、useFactory、useExisting - Token 可以是类名、字符串、Symbol
- 默认单例,可以改成 REQUEST 或 TRANSIENT 作用域
下一章会深入讲解依赖注入的机制,搞清楚 NestJS 的 IoC 容器到底是怎么工作的。