nestjs-dependency-injection
Compare original and translation side by side
🇺🇸
Original
English🇨🇳
Translation
ChineseNestJS Dependency Injection
NestJS 依赖注入
Master NestJS dependency injection for building modular, testable
Node.js applications with proper service architecture, provider
patterns, and module organization.
精通NestJS依赖注入,助力构建模块化、可测试的Node.js应用,掌握合理的服务架构、提供者模式和模块组织方式。
Table of Contents
目录
Provider Patterns
提供者模式
Class Providers (Standard Pattern)
类提供者(标准模式)
typescript
import { Injectable } from '@nestjs/common';
@Injectable()
export class UserService {
private users: User[] = [];
findAll(): User[] {
return this.users;
}
findById(id: string): User | undefined {
return this.users.find(user => user.id === id);
}
create(user: User): User {
this.users.push(user);
return user;
}
}
// Module registration
@Module({
providers: [UserService],
exports: [UserService],
})
export class UserModule {}typescript
import { Injectable } from '@nestjs/common';
@Injectable()
export class UserService {
private users: User[] = [];
findAll(): User[] {
return this.users;
}
findById(id: string): User | undefined {
return this.users.find(user => user.id === id);
}
create(user: User): User {
this.users.push(user);
return user;
}
}
// 模块注册
@Module({
providers: [UserService],
exports: [UserService],
})
export class UserModule {}Value Providers
值提供者
typescript
import { Module } from '@nestjs/common';
// Simple value provider
const DATABASE_CONNECTION = {
provide: 'DATABASE_CONNECTION',
useValue: {
host: 'localhost',
port: 5432,
database: 'mydb',
},
};
// Configuration value provider
const APP_CONFIG = {
provide: 'APP_CONFIG',
useValue: {
apiUrl: process.env.API_URL,
timeout: 5000,
retries: 3,
},
};
@Module({
providers: [DATABASE_CONNECTION, APP_CONFIG],
exports: [DATABASE_CONNECTION, APP_CONFIG],
})
export class ConfigModule {}
// Usage in service
@Injectable()
export class ApiService {
constructor(
@Inject('APP_CONFIG') private config: AppConfig,
) {}
async fetchData(): Promise<any> {
const response = await fetch(this.config.apiUrl, {
timeout: this.config.timeout,
});
return response.json();
}
}typescript
import { Module } from '@nestjs/common';
// 简单值提供者
const DATABASE_CONNECTION = {
provide: 'DATABASE_CONNECTION',
useValue: {
host: 'localhost',
port: 5432,
database: 'mydb',
},
};
// 配置值提供者
const APP_CONFIG = {
provide: 'APP_CONFIG',
useValue: {
apiUrl: process.env.API_URL,
timeout: 5000,
retries: 3,
},
};
@Module({
providers: [DATABASE_CONNECTION, APP_CONFIG],
exports: [DATABASE_CONNECTION, APP_CONFIG],
})
export class ConfigModule {}
// 在服务中使用
@Injectable()
export class ApiService {
constructor(
@Inject('APP_CONFIG') private config: AppConfig,
) {}
async fetchData(): Promise<any> {
const response = await fetch(this.config.apiUrl, {
timeout: this.config.timeout,
});
return response.json();
}
}Factory Providers
工厂提供者
typescript
import { Injectable, Module } from '@nestjs/common';
// Simple factory provider
const CONNECTION_FACTORY = {
provide: 'DATABASE_CONNECTION',
useFactory: () => {
return createConnection({
type: 'postgres',
host: process.env.DB_HOST,
port: parseInt(process.env.DB_PORT),
database: process.env.DB_NAME,
});
},
};
// Factory with dependencies
const CACHE_MANAGER = {
provide: 'CACHE_MANAGER',
useFactory: (config: ConfigService) => {
return createCacheManager({
store: config.get('CACHE_STORE'),
ttl: config.get('CACHE_TTL'),
max: config.get('CACHE_MAX_ITEMS'),
});
},
inject: [ConfigService],
};
@Module({
providers: [
ConfigService,
CONNECTION_FACTORY,
CACHE_MANAGER,
],
exports: ['DATABASE_CONNECTION', 'CACHE_MANAGER'],
})
export class DatabaseModule {}typescript
import { Injectable, Module } from '@nestjs/common';
// 简单工厂提供者
const CONNECTION_FACTORY = {
provide: 'DATABASE_CONNECTION',
useFactory: () => {
return createConnection({
type: 'postgres',
host: process.env.DB_HOST,
port: parseInt(process.env.DB_PORT),
database: process.env.DB_NAME,
});
},
};
// 带依赖的工厂
const CACHE_MANAGER = {
provide: 'CACHE_MANAGER',
useFactory: (config: ConfigService) => {
return createCacheManager({
store: config.get('CACHE_STORE'),
ttl: config.get('CACHE_TTL'),
max: config.get('CACHE_MAX_ITEMS'),
});
},
inject: [ConfigService],
};
@Module({
providers: [
ConfigService,
CONNECTION_FACTORY,
CACHE_MANAGER,
],
exports: ['DATABASE_CONNECTION', 'CACHE_MANAGER'],
})
export class DatabaseModule {}Async Providers with useFactory
基于useFactory的异步提供者
typescript
import { Module } from '@nestjs/common';
import { ConfigService } from '@nestjs/config';
const DATABASE_PROVIDER = {
provide: 'DATABASE_CONNECTION',
useFactory: async (config: ConfigService) => {
const connection = await createConnection({
type: 'postgres',
host: config.get('DB_HOST'),
port: config.get('DB_PORT'),
username: config.get('DB_USER'),
password: config.get('DB_PASSWORD'),
database: config.get('DB_NAME'),
});
await connection.runMigrations();
return connection;
},
inject: [ConfigService],
};
const REDIS_PROVIDER = {
provide: 'REDIS_CLIENT',
useFactory: async (config: ConfigService) => {
const client = createClient({
url: config.get('REDIS_URL'),
});
await client.connect();
client.on('error', (err) => {
console.error('Redis error:', err);
});
return client;
},
inject: [ConfigService],
};
@Module({
providers: [DATABASE_PROVIDER, REDIS_PROVIDER],
exports: ['DATABASE_CONNECTION', 'REDIS_CLIENT'],
})
export class DataModule {}typescript
import { Module } from '@nestjs/common';
import { ConfigService } from '@nestjs/config';
const DATABASE_PROVIDER = {
provide: 'DATABASE_CONNECTION',
useFactory: async (config: ConfigService) => {
const connection = await createConnection({
type: 'postgres',
host: config.get('DB_HOST'),
port: config.get('DB_PORT'),
username: config.get('DB_USER'),
password: config.get('DB_PASSWORD'),
database: config.get('DB_NAME'),
});
await connection.runMigrations();
return connection;
},
inject: [ConfigService],
};
const REDIS_PROVIDER = {
provide: 'REDIS_CLIENT',
useFactory: async (config: ConfigService) => {
const client = createClient({
url: config.get('REDIS_URL'),
});
await client.connect();
client.on('error', (err) => {
console.error('Redis error:', err);
});
return client;
},
inject: [ConfigService],
};
@Module({
providers: [DATABASE_PROVIDER, REDIS_PROVIDER],
exports: ['DATABASE_CONNECTION', 'REDIS_CLIENT'],
})
export class DataModule {}Token-Based Injection with String Tokens
基于字符串令牌的令牌式注入
typescript
import { Inject, Injectable, Module } from '@nestjs/common';
// Define string tokens as constants
export const LOGGER_TOKEN = 'LOGGER';
export const METRICS_TOKEN = 'METRICS';
export const API_CLIENT_TOKEN = 'API_CLIENT';
// Provider definitions
const LOGGER_PROVIDER = {
provide: LOGGER_TOKEN,
useFactory: () => {
return createLogger({
level: process.env.LOG_LEVEL || 'info',
format: 'json',
});
},
};
const METRICS_PROVIDER = {
provide: METRICS_TOKEN,
useValue: createMetricsClient(),
};
@Module({
providers: [LOGGER_PROVIDER, METRICS_PROVIDER],
exports: [LOGGER_TOKEN, METRICS_TOKEN],
})
export class ObservabilityModule {}
// Usage in service
@Injectable()
export class UserService {
constructor(
@Inject(LOGGER_TOKEN) private logger: Logger,
@Inject(METRICS_TOKEN) private metrics: MetricsClient,
) {}
async createUser(data: CreateUserDto): Promise<User> {
this.logger.info('Creating user', { email: data.email });
this.metrics.increment('user.created');
const user = await this.repository.create(data);
return user;
}
}typescript
import { Inject, Injectable, Module } from '@nestjs/common';
// 将字符串令牌定义为常量
export const LOGGER_TOKEN = 'LOGGER';
export const METRICS_TOKEN = 'METRICS';
export const API_CLIENT_TOKEN = 'API_CLIENT';
// 提供者定义
const LOGGER_PROVIDER = {
provide: LOGGER_TOKEN,
useFactory: () => {
return createLogger({
level: process.env.LOG_LEVEL || 'info',
format: 'json',
});
},
};
const METRICS_PROVIDER = {
provide: METRICS_TOKEN,
useValue: createMetricsClient(),
};
@Module({
providers: [LOGGER_PROVIDER, METRICS_PROVIDER],
exports: [LOGGER_TOKEN, METRICS_TOKEN],
})
export class ObservabilityModule {}
// 在服务中使用
@Injectable()
export class UserService {
constructor(
@Inject(LOGGER_TOKEN) private logger: Logger,
@Inject(METRICS_TOKEN) private metrics: MetricsClient,
) {}
async createUser(data: CreateUserDto): Promise<User> {
this.logger.info('Creating user', { email: data.email });
this.metrics.increment('user.created');
const user = await this.repository.create(data);
return user;
}
}Token-Based Injection with Symbol Tokens
基于符号令牌的令牌式注入
typescript
import { Inject, Injectable, Module } from '@nestjs/common';
// Define symbol tokens for better type safety
export const DATABASE_CONNECTION = Symbol('DATABASE_CONNECTION');
export const CACHE_MANAGER = Symbol('CACHE_MANAGER');
export const EVENT_BUS = Symbol('EVENT_BUS');
// Provider with symbol token
const DB_PROVIDER = {
provide: DATABASE_CONNECTION,
useFactory: async () => {
return await createDatabaseConnection();
},
};
const CACHE_PROVIDER = {
provide: CACHE_MANAGER,
useClass: RedisCacheManager,
};
@Module({
providers: [DB_PROVIDER, CACHE_PROVIDER],
exports: [DATABASE_CONNECTION, CACHE_MANAGER],
})
export class InfrastructureModule {}
// Usage with symbol tokens
@Injectable()
export class ProductService {
constructor(
@Inject(DATABASE_CONNECTION) private db: Connection,
@Inject(CACHE_MANAGER) private cache: CacheManager,
) {}
async findById(id: string): Promise<Product> {
const cached = await this.cache.get(`product:${id}`);
if (cached) return cached;
const product = await this.db
.getRepository(Product)
.findOne({ where: { id } });
await this.cache.set(`product:${id}`, product, 3600);
return product;
}
}typescript
import { Inject, Injectable, Module } from '@nestjs/common';
// 定义符号令牌以获得更好的类型安全性
export const DATABASE_CONNECTION = Symbol('DATABASE_CONNECTION');
export const CACHE_MANAGER = Symbol('CACHE_MANAGER');
export const EVENT_BUS = Symbol('EVENT_BUS');
// 带符号令牌的提供者
const DB_PROVIDER = {
provide: DATABASE_CONNECTION,
useFactory: async () => {
return await createDatabaseConnection();
},
};
const CACHE_PROVIDER = {
provide: CACHE_MANAGER,
useClass: RedisCacheManager,
};
@Module({
providers: [DB_PROVIDER, CACHE_PROVIDER],
exports: [DATABASE_CONNECTION, CACHE_MANAGER],
})
export class InfrastructureModule {}
// 使用符号令牌注入
@Injectable()
export class ProductService {
constructor(
@Inject(DATABASE_CONNECTION) private db: Connection,
@Inject(CACHE_MANAGER) private cache: CacheManager,
) {}
async findById(id: string): Promise<Product> {
const cached = await this.cache.get(`product:${id}`);
if (cached) return cached;
const product = await this.db
.getRepository(Product)
.findOne({ where: { id } });
await this.cache.set(`product:${id}`, product, 3600);
return product;
}
}Optional Dependencies with @Optional()
带@Optional()的可选依赖
typescript
import { Injectable, Optional, Inject } from '@nestjs/common';
@Injectable()
export class NotificationService {
constructor(
@Optional()
@Inject('EMAIL_SERVICE')
private emailService?: EmailService,
@Optional()
@Inject('SMS_SERVICE')
private smsService?: SmsService,
) {}
async notify(user: User, message: string): Promise<void> {
// Gracefully handle missing optional dependencies
if (this.emailService) {
await this.emailService.send(user.email, message);
}
if (this.smsService && user.phone) {
await this.smsService.send(user.phone, message);
}
// Always have a fallback notification method
await this.logNotification(user, message);
}
private async logNotification(
user: User,
message: string,
): Promise<void> {
console.log(`Notification for ${user.id}: ${message}`);
}
}
// Module with optional providers
@Module({
providers: [
NotificationService,
// EMAIL_SERVICE might not be registered
// SMS_SERVICE might not be registered
],
exports: [NotificationService],
})
export class NotificationModule {}typescript
import { Injectable, Optional, Inject } from '@nestjs/common';
@Injectable()
export class NotificationService {
constructor(
@Optional()
@Inject('EMAIL_SERVICE')
private emailService?: EmailService,
@Optional()
@Inject('SMS_SERVICE')
private smsService?: SmsService,
) {}
async notify(user: User, message: string): Promise<void> {
// 优雅处理缺失的可选依赖
if (this.emailService) {
await this.emailService.send(user.email, message);
}
if (this.smsService && user.phone) {
await this.smsService.send(user.phone, message);
}
// 始终保留备用通知方式
await this.logNotification(user, message);
}
private async logNotification(
user: User,
message: string,
): Promise<void> {
console.log(`Notification for ${user.id}: ${message}`);
}
}
// 带可选提供者的模块
@Module({
providers: [
NotificationService,
// EMAIL_SERVICE 可能未注册
// SMS_SERVICE 可能未注册
],
exports: [NotificationService],
})
export class NotificationModule {}Property-Based Injection
基于属性的注入
typescript
import { Injectable, Inject } from '@nestjs/common';
// Property-based injection (less preferred)
@Injectable()
export class PaymentService {
@Inject('PAYMENT_GATEWAY')
private paymentGateway: PaymentGateway;
@Inject('FRAUD_DETECTOR')
private fraudDetector: FraudDetector;
async processPayment(
amount: number,
card: CardDetails,
): Promise<PaymentResult> {
const isFraudulent = await this.fraudDetector.check(card);
if (isFraudulent) {
throw new FraudDetectedException();
}
return await this.paymentGateway.charge(amount, card);
}
}
// Constructor-based injection (preferred)
@Injectable()
export class OrderService {
constructor(
@Inject('PAYMENT_SERVICE')
private readonly paymentService: PaymentService,
@Inject('INVENTORY_SERVICE')
private readonly inventoryService: InventoryService,
) {}
async createOrder(data: CreateOrderDto): Promise<Order> {
await this.inventoryService.reserve(data.items);
await this.paymentService.processPayment(
data.total,
data.card,
);
return await this.repository.create(data);
}
}typescript
import { Injectable, Inject } from '@nestjs/common';
// 基于属性的注入(不太推荐)
@Injectable()
export class PaymentService {
@Inject('PAYMENT_GATEWAY')
private paymentGateway: PaymentGateway;
@Inject('FRAUD_DETECTOR')
private fraudDetector: FraudDetector;
async processPayment(
amount: number,
card: CardDetails,
): Promise<PaymentResult> {
const isFraudulent = await this.fraudDetector.check(card);
if (isFraudulent) {
throw new FraudDetectedException();
}
return await this.paymentGateway.charge(amount, card);
}
}
// 基于构造函数的注入(推荐)
@Injectable()
export class OrderService {
constructor(
@Inject('PAYMENT_SERVICE')
private readonly paymentService: PaymentService,
@Inject('INVENTORY_SERVICE')
private readonly inventoryService: InventoryService,
) {}
async createOrder(data: CreateOrderDto): Promise<Order> {
await this.inventoryService.reserve(data.items);
await this.paymentService.processPayment(
data.total,
data.card,
);
return await this.repository.create(data);
}
}Class Provider with useClass
带useClass的类提供者
typescript
import { Injectable, Module } from '@nestjs/common';
// Abstract interface
export abstract class LoggerService {
abstract log(message: string): void;
abstract error(message: string, trace: string): void;
}
// Concrete implementations
@Injectable()
export class ConsoleLoggerService extends LoggerService {
log(message: string): void {
console.log(message);
}
error(message: string, trace: string): void {
console.error(message, trace);
}
}
@Injectable()
export class FileLoggerService extends LoggerService {
log(message: string): void {
fs.appendFileSync('app.log', `${message}\n`);
}
error(message: string, trace: string): void {
fs.appendFileSync('error.log', `${message}\n${trace}\n`);
}
}
// Use different implementations based on environment
@Module({
providers: [
{
provide: LoggerService,
useClass:
process.env.NODE_ENV === 'production'
? FileLoggerService
: ConsoleLoggerService,
},
],
exports: [LoggerService],
})
export class LoggerModule {}
// Usage
@Injectable()
export class AppService {
constructor(private readonly logger: LoggerService) {}
doSomething(): void {
this.logger.log('Operation completed');
}
}typescript
import { Injectable, Module } from '@nestjs/common';
// 抽象接口
export abstract class LoggerService {
abstract log(message: string): void;
abstract error(message: string, trace: string): void;
}
// 具体实现
@Injectable()
export class ConsoleLoggerService extends LoggerService {
log(message: string): void {
console.log(message);
}
error(message: string, trace: string): void {
console.error(message, trace);
}
}
@Injectable()
export class FileLoggerService extends LoggerService {
log(message: string): void {
fs.appendFileSync('app.log', `${message}\n`);
}
error(message: string, trace: string): void {
fs.appendFileSync('error.log', `${message}\n${trace}\n`);
}
}
// 根据环境使用不同的实现
@Module({
providers: [
{
provide: LoggerService,
useClass:
process.env.NODE_ENV === 'production'
? FileLoggerService
: ConsoleLoggerService,
},
],
exports: [LoggerService],
})
export class LoggerModule {}
// 使用
@Injectable()
export class AppService {
constructor(private readonly logger: LoggerService) {}
doSomething(): void {
this.logger.log('Operation completed');
}
}Alias Providers (useExisting)
别名提供者(useExisting)
typescript
import { Injectable, Module } from '@nestjs/common';
@Injectable()
export class UserService {
findAll(): User[] {
return [];
}
}
// Create an alias for the service
@Module({
providers: [
UserService,
{
provide: 'IUserService',
useExisting: UserService,
},
{
provide: 'UserRepository',
useExisting: UserService,
},
],
exports: [
UserService,
'IUserService',
'UserRepository',
],
})
export class UserModule {}
// Can inject using any of the tokens
@Injectable()
export class ReportService {
constructor(
@Inject('IUserService') private userService: UserService,
) {}
async generateReport(): Promise<Report> {
const users = this.userService.findAll();
return this.buildReport(users);
}
}typescript
import { Injectable, Module } from '@nestjs/common';
@Injectable()
export class UserService {
findAll(): User[] {
return [];
}
}
// 为服务创建别名
@Module({
providers: [
UserService,
{
provide: 'IUserService',
useExisting: UserService,
},
{
provide: 'UserRepository',
useExisting: UserService,
},
],
exports: [
UserService,
'IUserService',
'UserRepository',
],
})
export class UserModule {}
// 可以使用任意令牌注入
@Injectable()
export class ReportService {
constructor(
@Inject('IUserService') private userService: UserService,
) {}
async generateReport(): Promise<Report> {
const users = this.userService.findAll();
return this.buildReport(users);
}
}Module System
模块系统
Module Organization and Encapsulation
模块组织与封装
typescript
import { Module } from '@nestjs/common';
// Feature module with proper encapsulation
@Module({
imports: [DatabaseModule, CacheModule],
providers: [
UserService,
UserRepository,
UserValidator,
],
controllers: [UserController],
exports: [UserService], // Only export what's needed
})
export class UserModule {}
// Domain module grouping related features
@Module({
imports: [
UserModule,
AuthModule,
ProfileModule,
],
})
export class IdentityModule {}
// Application module
@Module({
imports: [
ConfigModule.forRoot(),
IdentityModule,
ProductModule,
OrderModule,
],
})
export class AppModule {}typescript
import { Module } from '@nestjs/common';
// 具备良好封装性的功能模块
@Module({
imports: [DatabaseModule, CacheModule],
providers: [
UserService,
UserRepository,
UserValidator,
],
controllers: [UserController],
exports: [UserService], // 仅导出需要的内容
})
export class UserModule {}
// 聚合相关功能的领域模块
@Module({
imports: [
UserModule,
AuthModule,
ProfileModule,
],
})
export class IdentityModule {}
// 应用根模块
@Module({
imports: [
ConfigModule.forRoot(),
IdentityModule,
ProductModule,
OrderModule,
],
})
export class AppModule {}Global Modules with @Global()
带@Global()的全局模块
typescript
import { Module, Global } from '@nestjs/common';
// Global module available everywhere
@Global()
@Module({
providers: [
{
provide: 'LOGGER',
useFactory: () => createLogger(),
},
{
provide: 'CONFIG',
useValue: loadConfiguration(),
},
],
exports: ['LOGGER', 'CONFIG'],
})
export class CoreModule {}
// Usage in any module without importing
@Injectable()
export class AnyService {
constructor(
@Inject('LOGGER') private logger: Logger,
@Inject('CONFIG') private config: Config,
) {}
}
// Register global module once in AppModule
@Module({
imports: [
CoreModule, // Only import once
FeatureModule1,
FeatureModule2,
],
})
export class AppModule {}typescript
import { Module, Global } from '@nestjs/common';
// 全局模块,可在任意地方使用
@Global()
@Module({
providers: [
{
provide: 'LOGGER',
useFactory: () => createLogger(),
},
{
provide: 'CONFIG',
useValue: loadConfiguration(),
},
],
exports: ['LOGGER', 'CONFIG'],
})
export class CoreModule {}
// 无需导入即可在任意模块中使用
@Injectable()
export class AnyService {
constructor(
@Inject('LOGGER') private logger: Logger,
@Inject('CONFIG') private config: Config,
) {}
}
// 在AppModule中注册一次全局模块
@Module({
imports: [
CoreModule, // 仅需导入一次
FeatureModule1,
FeatureModule2,
],
})
export class AppModule {}Dynamic Modules with forRoot
带forRoot的动态模块
typescript
import { Module, DynamicModule, Provider } from '@nestjs/common';
export interface DatabaseModuleOptions {
host: string;
port: number;
username: string;
password: string;
database: string;
}
@Module({})
export class DatabaseModule {
static forRoot(
options: DatabaseModuleOptions,
): DynamicModule {
const connectionProvider: Provider = {
provide: 'DATABASE_CONNECTION',
useFactory: async () => {
return await createConnection(options);
},
};
return {
module: DatabaseModule,
providers: [
connectionProvider,
DatabaseService,
],
exports: [
'DATABASE_CONNECTION',
DatabaseService,
],
global: true,
};
}
}
// Usage in AppModule
@Module({
imports: [
DatabaseModule.forRoot({
host: 'localhost',
port: 5432,
username: 'admin',
password: 'secret',
database: 'myapp',
}),
],
})
export class AppModule {}typescript
import { Module, DynamicModule, Provider } from '@nestjs/common';
export interface DatabaseModuleOptions {
host: string;
port: number;
username: string;
password: string;
database: string;
}
@Module({})
export class DatabaseModule {
static forRoot(
options: DatabaseModuleOptions,
): DynamicModule {
const connectionProvider: Provider = {
provide: 'DATABASE_CONNECTION',
useFactory: async () => {
return await createConnection(options);
},
};
return {
module: DatabaseModule,
providers: [
connectionProvider,
DatabaseService,
],
exports: [
'DATABASE_CONNECTION',
DatabaseService,
],
global: true,
};
}
}
// 在AppModule中使用
@Module({
imports: [
DatabaseModule.forRoot({
host: 'localhost',
port: 5432,
username: 'admin',
password: 'secret',
database: 'myapp',
}),
],
})
export class AppModule {}Dynamic Modules with forRootAsync
带forRootAsync的动态模块
typescript
import {
Module,
DynamicModule,
Provider,
} from '@nestjs/common';
import { ConfigService } from '@nestjs/config';
export interface CacheModuleAsyncOptions {
useFactory: (...args: any[]) => Promise<CacheOptions>;
inject?: any[];
}
@Module({})
export class CacheModule {
static forRootAsync(
options: CacheModuleAsyncOptions,
): DynamicModule {
const cacheOptionsProvider: Provider = {
provide: 'CACHE_OPTIONS',
useFactory: options.useFactory,
inject: options.inject || [],
};
const cacheProvider: Provider = {
provide: 'CACHE_MANAGER',
useFactory: async (cacheOptions: CacheOptions) => {
return await createCacheManager(cacheOptions);
},
inject: ['CACHE_OPTIONS'],
};
return {
module: CacheModule,
providers: [
cacheOptionsProvider,
cacheProvider,
CacheService,
],
exports: ['CACHE_MANAGER', CacheService],
global: true,
};
}
}
// Usage with ConfigService
@Module({
imports: [
ConfigModule.forRoot(),
CacheModule.forRootAsync({
useFactory: async (config: ConfigService) => ({
store: config.get('CACHE_STORE'),
ttl: config.get('CACHE_TTL'),
max: config.get('CACHE_MAX_ITEMS'),
}),
inject: [ConfigService],
}),
],
})
export class AppModule {}typescript
import {
Module,
DynamicModule,
Provider,
} from '@nestjs/common';
import { ConfigService } from '@nestjs/config';
export interface CacheModuleAsyncOptions {
useFactory: (...args: any[]) => Promise<CacheOptions>;
inject?: any[];
}
@Module({})
export class CacheModule {
static forRootAsync(
options: CacheModuleAsyncOptions,
): DynamicModule {
const cacheOptionsProvider: Provider = {
provide: 'CACHE_OPTIONS',
useFactory: options.useFactory,
inject: options.inject || [],
};
const cacheProvider: Provider = {
provide: 'CACHE_MANAGER',
useFactory: async (cacheOptions: CacheOptions) => {
return await createCacheManager(cacheOptions);
},
inject: ['CACHE_OPTIONS'],
};
return {
module: CacheModule,
providers: [
cacheOptionsProvider,
cacheProvider,
CacheService,
],
exports: ['CACHE_MANAGER', CacheService],
global: true,
};
}
}
// 结合ConfigService使用
@Module({
imports: [
ConfigModule.forRoot(),
CacheModule.forRootAsync({
useFactory: async (config: ConfigService) => ({
store: config.get('CACHE_STORE'),
ttl: config.get('CACHE_TTL'),
max: config.get('CACHE_MAX_ITEMS'),
}),
inject: [ConfigService],
}),
],
})
export class AppModule {}Module Re-exporting
模块重导出
typescript
import { Module } from '@nestjs/common';
// Low-level infrastructure modules
@Module({
providers: [DatabaseService],
exports: [DatabaseService],
})
export class DatabaseModule {}
@Module({
providers: [CacheService],
exports: [CacheService],
})
export class CacheModule {}
@Module({
providers: [QueueService],
exports: [QueueService],
})
export class QueueModule {}
// Shared module that re-exports common services
@Module({
imports: [
DatabaseModule,
CacheModule,
QueueModule,
],
exports: [
DatabaseModule,
CacheModule,
QueueModule,
],
})
export class SharedModule {}
// Feature modules import SharedModule instead of individual modules
@Module({
imports: [SharedModule],
providers: [UserService],
controllers: [UserController],
})
export class UserModule {}
@Module({
imports: [SharedModule],
providers: [ProductService],
controllers: [ProductController],
})
export class ProductModule {}typescript
import { Module } from '@nestjs/common';
// 底层基础设施模块
@Module({
providers: [DatabaseService],
exports: [DatabaseService],
})
export class DatabaseModule {}
@Module({
providers: [CacheService],
exports: [CacheService],
})
export class CacheModule {}
@Module({
providers: [QueueService],
exports: [QueueService],
})
export class QueueModule {}
// 重导出通用服务的共享模块
@Module({
imports: [
DatabaseModule,
CacheModule,
QueueModule,
],
exports: [
DatabaseModule,
CacheModule,
QueueModule,
],
})
export class SharedModule {}
// 功能模块导入SharedModule而非单独模块
@Module({
imports: [SharedModule],
providers: [UserService],
controllers: [UserController],
})
export class UserModule {}
@Module({
imports: [SharedModule],
providers: [ProductService],
controllers: [ProductController],
})
export class ProductModule {}Circular Dependencies Handling
循环依赖处理
typescript
import { Module, forwardRef } from '@nestjs/common';
// UserModule depends on AuthModule
@Module({
imports: [forwardRef(() => AuthModule)],
providers: [UserService],
exports: [UserService],
})
export class UserModule {}
// AuthModule depends on UserModule
@Module({
imports: [forwardRef(() => UserModule)],
providers: [AuthService],
exports: [AuthService],
})
export class AuthModule {}
// Service-level circular dependency
@Injectable()
export class UserService {
constructor(
@Inject(forwardRef(() => AuthService))
private authService: AuthService,
) {}
}
@Injectable()
export class AuthService {
constructor(
@Inject(forwardRef(() => UserService))
private userService: UserService,
) {}
}typescript
import { Module, forwardRef } from '@nestjs/common';
// UserModule依赖于AuthModule
@Module({
imports: [forwardRef(() => AuthModule)],
providers: [UserService],
exports: [UserService],
})
export class UserModule {}
// AuthModule依赖于UserModule
@Module({
imports: [forwardRef(() => UserModule)],
providers: [AuthService],
exports: [AuthService],
})
export class AuthModule {}
// 服务级别的循环依赖
@Injectable()
export class UserService {
constructor(
@Inject(forwardRef(() => AuthService))
private authService: AuthService,
) {}
}
@Injectable()
export class AuthService {
constructor(
@Inject(forwardRef(() => UserService))
private userService: UserService,
) {}
}Feature Modules with Lazy Loading
带懒加载的功能模块
typescript
import { Module } from '@nestjs/common';
// Admin feature module
@Module({
imports: [SharedModule],
providers: [
AdminService,
AdminGuard,
],
controllers: [AdminController],
})
export class AdminModule {}
// Lazy load admin module only when needed
@Module({
imports: [
// Other modules loaded eagerly
CoreModule,
UserModule,
],
})
export class AppModule {}
// In a controller or service, load AdminModule dynamically
@Injectable()
export class AppService {
constructor(private readonly lazyModuleLoader: LazyModuleLoader) {}
async performAdminTask(): Promise<void> {
const moduleRef = await this.lazyModuleLoader.load(
() => AdminModule,
);
const adminService = moduleRef.get(AdminService);
await adminService.doAdminWork();
}
}typescript
import { Module } from '@nestjs/common';
// 管理员功能模块
@Module({
imports: [SharedModule],
providers: [
AdminService,
AdminGuard,
],
controllers: [AdminController],
})
export class AdminModule {}
// 仅在需要时懒加载管理员模块
@Module({
imports: [
// 其他模块预加载
CoreModule,
UserModule,
],
})
export class AppModule {}
// 在控制器或服务中动态加载AdminModule
@Injectable()
export class AppService {
constructor(private readonly lazyModuleLoader: LazyModuleLoader) {}
async performAdminTask(): Promise<void> {
const moduleRef = await this.lazyModuleLoader.load(
() => AdminModule,
);
const adminService = moduleRef.get(AdminService);
await adminService.doAdminWork();
}
}Module Configuration Pattern
模块配置模式
typescript
import { Module, DynamicModule } from '@nestjs/common';
export interface EmailModuleOptions {
from: string;
host: string;
port: number;
secure: boolean;
}
@Module({})
export class EmailModule {
static forRoot(
options: EmailModuleOptions,
): DynamicModule {
return {
module: EmailModule,
providers: [
{
provide: 'EMAIL_OPTIONS',
useValue: options,
},
EmailService,
],
exports: [EmailService],
};
}
static forFeature(): DynamicModule {
return {
module: EmailModule,
providers: [EmailTemplateService],
exports: [EmailTemplateService],
};
}
}
// Root module configuration
@Module({
imports: [
EmailModule.forRoot({
from: 'noreply@example.com',
host: 'smtp.example.com',
port: 587,
secure: true,
}),
],
})
export class AppModule {}
// Feature module usage
@Module({
imports: [EmailModule.forFeature()],
providers: [NotificationService],
})
export class NotificationModule {}typescript
import { Module, DynamicModule } from '@nestjs/common';
export interface EmailModuleOptions {
from: string;
host: string;
port: number;
secure: boolean;
}
@Module({})
export class EmailModule {
static forRoot(
options: EmailModuleOptions,
): DynamicModule {
return {
module: EmailModule,
providers: [
{
provide: 'EMAIL_OPTIONS',
useValue: options,
},
EmailService,
],
exports: [EmailService],
};
}
static forFeature(): DynamicModule {
return {
module: EmailModule,
providers: [EmailTemplateService],
exports: [EmailTemplateService],
};
}
}
// 根模块配置
@Module({
imports: [
EmailModule.forRoot({
from: 'noreply@example.com',
host: 'smtp.example.com',
port: 587,
secure: true,
}),
],
})
export class AppModule {}
// 功能模块使用
@Module({
imports: [EmailModule.forFeature()],
providers: [NotificationService],
})
export class NotificationModule {}Shared Module Pattern
共享模块模式
typescript
import { Module, Global } from '@nestjs/common';
// Shared utilities module
@Global()
@Module({
providers: [
DateService,
StringService,
ValidationService,
],
exports: [
DateService,
StringService,
ValidationService,
],
})
export class UtilsModule {}
// Shared data access module
@Module({
providers: [
DataSource,
TransactionManager,
UnitOfWork,
],
exports: [
DataSource,
TransactionManager,
UnitOfWork,
],
})
export class DataAccessModule {}
// Combine into SharedModule
@Module({
imports: [
UtilsModule,
DataAccessModule,
],
exports: [
UtilsModule,
DataAccessModule,
],
})
export class SharedModule {}typescript
import { Module, Global } from '@nestjs/common';
// 共享工具模块
@Global()
@Module({
providers: [
DateService,
StringService,
ValidationService,
],
exports: [
DateService,
StringService,
ValidationService,
],
})
export class UtilsModule {}
// 共享数据访问模块
@Module({
providers: [
DataSource,
TransactionManager,
UnitOfWork,
],
exports: [
DataSource,
TransactionManager,
UnitOfWork,
],
})
export class DataAccessModule {}
// 组合为SharedModule
@Module({
imports: [
UtilsModule,
DataAccessModule,
],
exports: [
UtilsModule,
DataAccessModule,
],
})
export class SharedModule {}Injection Scopes
注入作用域
DEFAULT Scope (Singleton)
DEFAULT作用域(单例)
typescript
import { Injectable, Scope } from '@nestjs/common';
// Default scope - single instance shared across the app
@Injectable()
export class ConfigService {
private config: Record<string, any>;
constructor() {
this.config = this.loadConfiguration();
}
get(key: string): any {
return this.config[key];
}
private loadConfiguration(): Record<string, any> {
// Loaded once when application starts
return {
apiUrl: process.env.API_URL,
dbHost: process.env.DB_HOST,
};
}
}
// Singleton service with state
@Injectable()
export class CacheService {
private cache = new Map<string, any>();
set(key: string, value: any): void {
this.cache.set(key, value);
}
get(key: string): any {
return this.cache.get(key);
}
// State is shared across all requests
clear(): void {
this.cache.clear();
}
}typescript
import { Injectable, Scope } from '@nestjs/common';
// 默认作用域 - 应用内共享单个实例
@Injectable()
export class ConfigService {
private config: Record<string, any>;
constructor() {
this.config = this.loadConfiguration();
}
get(key: string): any {
return this.config[key];
}
private loadConfiguration(): Record<string, any> {
// 应用启动时加载一次
return {
apiUrl: process.env.API_URL,
dbHost: process.env.DB_HOST,
};
}
}
// 带状态的单例服务
@Injectable()
export class CacheService {
private cache = new Map<string, any>();
set(key: string, value: any): void {
this.cache.set(key, value);
}
get(key: string): any {
return this.cache.get(key);
}
// 状态在所有请求间共享
clear(): void {
this.cache.clear();
}
}REQUEST Scope with Performance Implications
REQUEST作用域及性能影响
typescript
import { Injectable, Scope, Inject } from '@nestjs/common';
import { REQUEST } from '@nestjs/core';
import { Request } from 'express';
// Request-scoped provider - new instance per request
@Injectable({ scope: Scope.REQUEST })
export class RequestContextService {
constructor(@Inject(REQUEST) private request: Request) {}
getUserId(): string {
return this.request.user?.id;
}
getTenantId(): string {
return this.request.headers['x-tenant-id'] as string;
}
getTraceId(): string {
return this.request.headers['x-trace-id'] as string;
}
}
// Service that depends on request-scoped provider
@Injectable({ scope: Scope.REQUEST })
export class AuditService {
constructor(
private readonly context: RequestContextService,
) {}
async logAction(action: string): Promise<void> {
await this.repository.create({
userId: this.context.getUserId(),
tenantId: this.context.getTenantId(),
action,
timestamp: new Date(),
});
}
}
// Performance consideration - all consumers become request-scoped
@Injectable({ scope: Scope.REQUEST })
export class UserService {
// This service is now created per request
// because it depends on a request-scoped service
constructor(
private readonly audit: AuditService,
) {
console.log('New UserService instance created');
}
async createUser(data: CreateUserDto): Promise<User> {
const user = await this.repository.create(data);
await this.audit.logAction('user.created');
return user;
}
}typescript
import { Injectable, Scope, Inject } from '@nestjs/common';
import { REQUEST } from '@nestjs/core';
import { Request } from 'express';
// 请求作用域提供者 - 每个请求创建新实例
@Injectable({ scope: Scope.REQUEST })
export class RequestContextService {
constructor(@Inject(REQUEST) private request: Request) {}
getUserId(): string {
return this.request.user?.id;
}
getTenantId(): string {
return this.request.headers['x-tenant-id'] as string;
}
getTraceId(): string {
return this.request.headers['x-trace-id'] as string;
}
}
// 依赖于请求作用域提供者的服务
@Injectable({ scope: Scope.REQUEST })
export class AuditService {
constructor(
private readonly context: RequestContextService,
) {}
async logAction(action: string): Promise<void> {
await this.repository.create({
userId: this.context.getUserId(),
tenantId: this.context.getTenantId(),
action,
timestamp: new Date(),
});
}
}
// 性能注意事项 - 所有依赖者都会变为请求作用域
@Injectable({ scope: Scope.REQUEST })
export class UserService {
// 该服务现在每个请求都会创建新实例
// 因为它依赖于请求作用域的服务
constructor(
private readonly audit: AuditService,
) {
console.log('New UserService instance created');
}
async createUser(data: CreateUserDto): Promise<User> {
const user = await this.repository.create(data);
await this.audit.logAction('user.created');
return user;
}
}TRANSIENT Scope
TRANSIENT作用域
typescript
import { Injectable, Scope } from '@nestjs/common';
// Transient scope - new instance every time it's injected
@Injectable({ scope: Scope.TRANSIENT })
export class UniqueIdGenerator {
private readonly id: string;
constructor() {
this.id = Math.random().toString(36).substring(7);
console.log(`New generator created with id: ${this.id}`);
}
generate(): string {
return `${this.id}-${Date.now()}`;
}
}
// Each injection point gets its own instance
@Injectable()
export class OrderService {
constructor(
private readonly idGen1: UniqueIdGenerator, // Instance 1
) {}
}
@Injectable()
export class InvoiceService {
constructor(
private readonly idGen2: UniqueIdGenerator, // Instance 2
) {}
}
// Transient for stateful operations
@Injectable({ scope: Scope.TRANSIENT })
export class QueryBuilder {
private conditions: string[] = [];
private params: any[] = [];
where(condition: string, ...params: any[]): this {
this.conditions.push(condition);
this.params.push(...params);
return this;
}
build(): { query: string; params: any[] } {
return {
query: `SELECT * FROM table WHERE ${this.conditions.join(' AND ')}`,
params: this.params,
};
}
}typescript
import { Injectable, Scope } from '@nestjs/common';
// 瞬时作用域 - 每次注入时创建新实例
@Injectable({ scope: Scope.TRANSIENT })
export class UniqueIdGenerator {
private readonly id: string;
constructor() {
this.id = Math.random().toString(36).substring(7);
console.log(`New generator created with id: ${this.id}`);
}
generate(): string {
return `${this.id}-${Date.now()}`;
}
}
// 每个注入点都会获得独立实例
@Injectable()
export class OrderService {
constructor(
private readonly idGen1: UniqueIdGenerator, // 实例1
) {}
}
@Injectable()
export class InvoiceService {
constructor(
private readonly idGen2: UniqueIdGenerator, // 实例2
) {}
}
// 用于有状态操作的瞬时作用域
@Injectable({ scope: Scope.TRANSIENT })
export class QueryBuilder {
private conditions: string[] = [];
private params: any[] = [];
where(condition: string, ...params: any[]): this {
this.conditions.push(condition);
this.params.push(...params);
return this;
}
build(): { query: string; params: any[] } {
return {
query: `SELECT * FROM table WHERE ${this.conditions.join(' AND ')}`,
params: this.params,
};
}
}Durable Providers
持久化提供者
typescript
import { Injectable, Scope } from '@nestjs/common';
// Durable provider - survives across requests
@Injectable({ scope: Scope.DEFAULT, durable: true })
export class ConnectionPoolService {
private pool: Pool;
constructor() {
this.pool = createPool({
host: 'localhost',
port: 5432,
max: 20,
});
}
getConnection(): Promise<Connection> {
return this.pool.connect();
}
async onModuleDestroy(): Promise<void> {
await this.pool.end();
}
}
// Durable request-scoped provider
@Injectable({
scope: Scope.REQUEST,
durable: true,
})
export class RequestLoggerService {
private logs: string[] = [];
log(message: string): void {
this.logs.push(message);
}
getLogs(): string[] {
return this.logs;
}
}typescript
import { Injectable, Scope } from '@nestjs/common';
// 持久化提供者 - 跨请求存活
@Injectable({ scope: Scope.DEFAULT, durable: true })
export class ConnectionPoolService {
private pool: Pool;
constructor() {
this.pool = createPool({
host: 'localhost',
port: 5432,
max: 20,
});
}
getConnection(): Promise<Connection> {
return this.pool.connect();
}
async onModuleDestroy(): Promise<void> {
await this.pool.end();
}
}
// 持久化请求作用域提供者
@Injectable({
scope: Scope.REQUEST,
durable: true,
})
export class RequestLoggerService {
private logs: string[] = [];
log(message: string): void {
this.logs.push(message);
}
getLogs(): string[] {
return this.logs;
}
}Scope Inheritance
作用域继承
typescript
import { Injectable, Scope } from '@nestjs/common';
// Parent service with DEFAULT scope
@Injectable()
export class DatabaseService {
query(sql: string): Promise<any> {
return this.pool.query(sql);
}
}
// Child service inherits scope from parent
@Injectable()
export class UserRepository {
constructor(private readonly db: DatabaseService) {}
findAll(): Promise<User[]> {
return this.db.query('SELECT * FROM users');
}
}
// Request-scoped parent
@Injectable({ scope: Scope.REQUEST })
export class RequestContext {
constructor(@Inject(REQUEST) private request: Request) {}
getTenantId(): string {
return this.request.headers['x-tenant-id'] as string;
}
}
// Child inherits REQUEST scope
@Injectable({ scope: Scope.REQUEST })
export class TenantService {
constructor(
private readonly context: RequestContext,
private readonly db: DatabaseService, // Still singleton
) {}
async getData(): Promise<any[]> {
const tenantId = this.context.getTenantId();
return this.db.query(
`SELECT * FROM data WHERE tenant_id = '${tenantId}'`,
);
}
}typescript
import { Injectable, Scope } from '@nestjs/common';
// 父服务为DEFAULT作用域
@Injectable()
export class DatabaseService {
query(sql: string): Promise<any> {
return this.pool.query(sql);
}
}
// 子服务继承父服务的作用域
@Injectable()
export class UserRepository {
constructor(private readonly db: DatabaseService) {}
findAll(): Promise<User[]> {
return this.db.query('SELECT * FROM users');
}
}
// 请求作用域的父服务
@Injectable({ scope: Scope.REQUEST })
export class RequestContext {
constructor(@Inject(REQUEST) private request: Request) {}
getTenantId(): string {
return this.request.headers['x-tenant-id'] as string;
}
}
// 子服务继承REQUEST作用域
@Injectable({ scope: Scope.REQUEST })
export class TenantService {
constructor(
private readonly context: RequestContext,
private readonly db: DatabaseService, // 仍然是单例
) {}
async getData(): Promise<any[]> {
const tenantId = this.context.getTenantId();
return this.db.query(
`SELECT * FROM data WHERE tenant_id = '${tenantId}'`,
);
}
}Scope Configuration in Modules
模块中的作用域配置
typescript
import { Module } from '@nestjs/common';
@Module({
providers: [
// Default scope (singleton)
ConfigService,
// Request scope
{
provide: 'REQUEST_LOGGER',
scope: Scope.REQUEST,
useClass: RequestLoggerService,
},
// Transient scope
{
provide: 'ID_GENERATOR',
scope: Scope.TRANSIENT,
useClass: IdGeneratorService,
},
],
})
export class AppModule {}typescript
import { Module } from '@nestjs/common';
@Module({
providers: [
// 默认作用域(单例)
ConfigService,
// 请求作用域
{
provide: 'REQUEST_LOGGER',
scope: Scope.REQUEST,
useClass: RequestLoggerService,
},
// 瞬时作用域
{
provide: 'ID_GENERATOR',
scope: Scope.TRANSIENT,
useClass: IdGeneratorService,
},
],
})
export class AppModule {}Advanced Patterns
高级模式
Custom Decorators for Injection
用于注入的自定义装饰器
typescript
import { Inject } from '@nestjs/common';
// Custom decorator for logger injection
export const InjectLogger = () => Inject('LOGGER');
// Custom decorator for repository injection
export function InjectRepository(
entity: Function,
): ParameterDecorator {
return Inject(`${entity.name}Repository`);
}
// Custom decorator with options
export function InjectCache(
namespace?: string,
): ParameterDecorator {
const token = namespace ? `CACHE:${namespace}` : 'CACHE';
return Inject(token);
}
// Usage in services
@Injectable()
export class UserService {
constructor(
@InjectLogger() private logger: Logger,
@InjectRepository(User) private repo: Repository<User>,
@InjectCache('users') private cache: CacheManager,
) {}
async findAll(): Promise<User[]> {
this.logger.log('Finding all users');
const cached = await this.cache.get('all');
if (cached) return cached;
const users = await this.repo.find();
await this.cache.set('all', users, 300);
return users;
}
}
// Register providers with custom tokens
@Module({
providers: [
{
provide: 'LOGGER',
useFactory: () => createLogger(),
},
{
provide: 'UserRepository',
useClass: UserRepository,
},
{
provide: 'CACHE:users',
useFactory: () => createCacheManager('users'),
},
],
})
export class UserModule {}typescript
import { Inject } from '@nestjs/common';
// 用于日志注入的自定义装饰器
export const InjectLogger = () => Inject('LOGGER');
// 用于仓库注入的自定义装饰器
export function InjectRepository(
entity: Function,
): ParameterDecorator {
return Inject(`${entity.name}Repository`);
}
// 带选项的自定义装饰器
export function InjectCache(
namespace?: string,
): ParameterDecorator {
const token = namespace ? `CACHE:${namespace}` : 'CACHE';
return Inject(token);
}
// 在服务中使用
@Injectable()
export class UserService {
constructor(
@InjectLogger() private logger: Logger,
@InjectRepository(User) private repo: Repository<User>,
@InjectCache('users') private cache: CacheManager,
) {}
async findAll(): Promise<User[]> {
this.logger.log('Finding all users');
const cached = await this.cache.get('all');
if (cached) return cached;
const users = await this.repo.find();
await this.cache.set('all', users, 300);
return users;
}
}
// 使用自定义令牌注册提供者
@Module({
providers: [
{
provide: 'LOGGER',
useFactory: () => createLogger(),
},
{
provide: 'UserRepository',
useClass: UserRepository,
},
{
provide: 'CACHE:users',
useFactory: () => createCacheManager('users'),
},
],
})
export class UserModule {}Provider Arrays and Multi-Providers
提供者数组与多提供者
typescript
import { Module, Inject } from '@nestjs/common';
// Define token for array of providers
export const EVENT_HANDLERS = 'EVENT_HANDLERS';
// Multiple implementations of event handler
@Injectable()
export class UserEventHandler implements EventHandler {
handle(event: Event): void {
console.log('User event:', event);
}
}
@Injectable()
export class AuditEventHandler implements EventHandler {
handle(event: Event): void {
console.log('Audit event:', event);
}
}
@Injectable()
export class NotificationEventHandler implements EventHandler {
handle(event: Event): void {
console.log('Notification event:', event);
}
}
// Module registering multiple providers
@Module({
providers: [
UserEventHandler,
AuditEventHandler,
NotificationEventHandler,
{
provide: EVENT_HANDLERS,
useFactory: (
userHandler: UserEventHandler,
auditHandler: AuditEventHandler,
notificationHandler: NotificationEventHandler,
) => [userHandler, auditHandler, notificationHandler],
inject: [
UserEventHandler,
AuditEventHandler,
NotificationEventHandler,
],
},
],
exports: [EVENT_HANDLERS],
})
export class EventModule {}
// Service using array of providers
@Injectable()
export class EventBus {
constructor(
@Inject(EVENT_HANDLERS)
private handlers: EventHandler[],
) {}
emit(event: Event): void {
this.handlers.forEach((handler) => {
handler.handle(event);
});
}
}typescript
import { Module, Inject } from '@nestjs/common';
// 定义提供者数组的令牌
export const EVENT_HANDLERS = 'EVENT_HANDLERS';
// 事件处理器的多个实现
@Injectable()
export class UserEventHandler implements EventHandler {
handle(event: Event): void {
console.log('User event:', event);
}
}
@Injectable()
export class AuditEventHandler implements EventHandler {
handle(event: Event): void {
console.log('Audit event:', event);
}
}
@Injectable()
export class NotificationEventHandler implements EventHandler {
handle(event: Event): void {
console.log('Notification event:', event);
}
}
// 注册多个提供者的模块
@Module({
providers: [
UserEventHandler,
AuditEventHandler,
NotificationEventHandler,
{
provide: EVENT_HANDLERS,
useFactory: (
userHandler: UserEventHandler,
auditHandler: AuditEventHandler,
notificationHandler: NotificationEventHandler,
) => [userHandler, auditHandler, notificationHandler],
inject: [
UserEventHandler,
AuditEventHandler,
NotificationEventHandler,
],
},
],
exports: [EVENT_HANDLERS],
})
export class EventModule {}
// 使用提供者数组的服务
@Injectable()
export class EventBus {
constructor(
@Inject(EVENT_HANDLERS)
private handlers: EventHandler[],
) {}
emit(event: Event): void {
this.handlers.forEach((handler) => {
handler.handle(event);
});
}
}Lazy Module Loading
懒加载模块
typescript
import {
Injectable,
LazyModuleLoader,
} from '@nestjs/common';
@Injectable()
export class ReportService {
constructor(
private readonly lazyModuleLoader: LazyModuleLoader,
) {}
async generateComplexReport(): Promise<Report> {
// Load heavy module only when needed
const moduleRef = await this.lazyModuleLoader.load(
() => import('./analytics/analytics.module')
.then((m) => m.AnalyticsModule),
);
const analyticsService = moduleRef.get(AnalyticsService);
const data = await analyticsService.analyze();
return this.buildReport(data);
}
async exportToPdf(report: Report): Promise<Buffer> {
// Load PDF module lazily
const moduleRef = await this.lazyModuleLoader.load(
() => import('./pdf/pdf.module')
.then((m) => m.PdfModule),
);
const pdfService = moduleRef.get(PdfService);
return await pdfService.generate(report);
}
}typescript
import {
Injectable,
LazyModuleLoader,
} from '@nestjs/common';
@Injectable()
export class ReportService {
constructor(
private readonly lazyModuleLoader: LazyModuleLoader,
) {}
async generateComplexReport(): Promise<Report> {
// 仅在需要时加载重型模块
const moduleRef = await this.lazyModuleLoader.load(
() => import('./analytics/analytics.module')
.then((m) => m.AnalyticsModule),
);
const analyticsService = moduleRef.get(AnalyticsService);
const data = await analyticsService.analyze();
return this.buildReport(data);
}
async exportToPdf(report: Report): Promise<Buffer> {
// 懒加载PDF模块
const moduleRef = await this.lazyModuleLoader.load(
() => import('./pdf/pdf.module')
.then((m) => m.PdfModule),
);
const pdfService = moduleRef.get(PdfService);
return await pdfService.generate(report);
}
}Testing with Dependency Injection
依赖注入测试
typescript
import { Test, TestingModule } from '@nestjs/testing';
describe('UserService', () => {
let service: UserService;
let repository: Repository<User>;
beforeEach(async () => {
const module: TestingModule = await Test.createTestingModule({
providers: [
UserService,
{
provide: 'UserRepository',
useValue: {
find: jest.fn(),
findOne: jest.fn(),
create: jest.fn(),
save: jest.fn(),
},
},
{
provide: 'LOGGER',
useValue: {
log: jest.fn(),
error: jest.fn(),
},
},
],
}).compile();
service = module.get<UserService>(UserService);
repository = module.get('UserRepository');
});
it('should find all users', async () => {
const users = [{ id: '1', name: 'John' }];
jest.spyOn(repository, 'find').mockResolvedValue(users);
const result = await service.findAll();
expect(result).toEqual(users);
expect(repository.find).toHaveBeenCalledTimes(1);
});
it('should create a user', async () => {
const createDto = { name: 'Jane', email: 'jane@example.com' };
const user = { id: '2', ...createDto };
jest.spyOn(repository, 'create').mockReturnValue(user);
jest.spyOn(repository, 'save').mockResolvedValue(user);
const result = await service.create(createDto);
expect(result).toEqual(user);
expect(repository.create).toHaveBeenCalledWith(createDto);
expect(repository.save).toHaveBeenCalledWith(user);
});
});
// Integration testing with test database
describe('UserService (integration)', () => {
let app: INestApplication;
let service: UserService;
beforeAll(async () => {
const moduleRef = await Test.createTestingModule({
imports: [
DatabaseModule.forRoot({
type: 'postgres',
host: 'localhost',
port: 5433,
database: 'test',
}),
UserModule,
],
}).compile();
app = moduleRef.createNestApplication();
await app.init();
service = moduleRef.get<UserService>(UserService);
});
afterAll(async () => {
await app.close();
});
it('should persist user to database', async () => {
const createDto = { name: 'Integration', email: 'test@test.com' };
const user = await service.create(createDto);
expect(user.id).toBeDefined();
expect(user.name).toBe(createDto.name);
const found = await service.findById(user.id);
expect(found).toEqual(user);
});
});typescript
import { Test, TestingModule } from '@nestjs/testing';
describe('UserService', () => {
let service: UserService;
let repository: Repository<User>;
beforeEach(async () => {
const module: TestingModule = await Test.createTestingModule({
providers: [
UserService,
{
provide: 'UserRepository',
useValue: {
find: jest.fn(),
findOne: jest.fn(),
create: jest.fn(),
save: jest.fn(),
},
},
{
provide: 'LOGGER',
useValue: {
log: jest.fn(),
error: jest.fn(),
},
},
],
}).compile();
service = module.get<UserService>(UserService);
repository = module.get('UserRepository');
});
it('should find all users', async () => {
const users = [{ id: '1', name: 'John' }];
jest.spyOn(repository, 'find').mockResolvedValue(users);
const result = await service.findAll();
expect(result).toEqual(users);
expect(repository.find).toHaveBeenCalledTimes(1);
});
it('should create a user', async () => {
const createDto = { name: 'Jane', email: 'jane@example.com' };
const user = { id: '2', ...createDto };
jest.spyOn(repository, 'create').mockReturnValue(user);
jest.spyOn(repository, 'save').mockResolvedValue(user);
const result = await service.create(createDto);
expect(result).toEqual(user);
expect(repository.create).toHaveBeenCalledWith(createDto);
expect(repository.save).toHaveBeenCalledWith(user);
});
});
// 使用测试数据库进行集成测试
describe('UserService (integration)', () => {
let app: INestApplication;
let service: UserService;
beforeAll(async () => {
const moduleRef = await Test.createTestingModule({
imports: [
DatabaseModule.forRoot({
type: 'postgres',
host: 'localhost',
port: 5433,
database: 'test',
}),
UserModule,
],
}).compile();
app = moduleRef.createNestApplication();
await app.init();
service = moduleRef.get<UserService>(UserService);
});
afterAll(async () => {
await app.close();
});
it('should persist user to database', async () => {
const createDto = { name: 'Integration', email: 'test@test.com' };
const user = await service.create(createDto);
expect(user.id).toBeDefined();
expect(user.name).toBe(createDto.name);
const found = await service.findById(user.id);
expect(found).toEqual(user);
});
});ModuleRef for Dynamic Provider Resolution
用于动态提供者解析的ModuleRef
typescript
import { Injectable, ModuleRef } from '@nestjs/core';
@Injectable()
export class DynamicService {
constructor(private readonly moduleRef: ModuleRef) {}
async processWithStrategy(
strategyName: string,
data: any,
): Promise<any> {
// Dynamically resolve provider at runtime
const strategy = this.moduleRef.get(
`${strategyName}Strategy`,
{ strict: false },
);
return await strategy.process(data);
}
async getServiceByTenant(tenantId: string): Promise<any> {
// Get service instance dynamically
const token = `TenantService:${tenantId}`;
try {
return this.moduleRef.get(token, { strict: false });
} catch {
// Fallback to default service
return this.moduleRef.get('DefaultTenantService');
}
}
}
// Using ModuleRef with request-scoped providers
@Injectable({ scope: Scope.REQUEST })
export class ContextAwareService {
constructor(private readonly moduleRef: ModuleRef) {}
async getCurrentUser(): Promise<User> {
// Get request-scoped context
const context = await this.moduleRef.resolve(
RequestContextService,
);
const userId = context.getUserId();
const userService = this.moduleRef.get(UserService);
return await userService.findById(userId);
}
}typescript
import { Injectable, ModuleRef } from '@nestjs/core';
@Injectable()
export class DynamicService {
constructor(private readonly moduleRef: ModuleRef) {}
async processWithStrategy(
strategyName: string,
data: any,
): Promise<any> {
// 在运行时动态解析提供者
const strategy = this.moduleRef.get(
`${strategyName}Strategy`,
{ strict: false },
);
return await strategy.process(data);
}
async getServiceByTenant(tenantId: string): Promise<any> {
// 动态获取服务实例
const token = `TenantService:${tenantId}`;
try {
return this.moduleRef.get(token, { strict: false });
} catch {
// 回退到默认服务
return this.moduleRef.get('DefaultTenantService');
}
}
}
// 将ModuleRef与请求作用域提供者结合使用
@Injectable({ scope: Scope.REQUEST })
export class ContextAwareService {
constructor(private readonly moduleRef: ModuleRef) {}
async getCurrentUser(): Promise<User> {
// 获取请求作用域上下文
const context = await this.moduleRef.resolve(
RequestContextService,
);
const userId = context.getUserId();
const userService = this.moduleRef.get(UserService);
return await userService.findById(userId);
}
}Plugin Pattern with Dependency Injection
基于依赖注入的插件模式
typescript
import { Module, DynamicModule, Type } from '@nestjs/common';
export interface Plugin {
name: string;
initialize(): Promise<void>;
execute(data: any): Promise<any>;
}
export interface PluginModuleOptions {
plugins: Type<Plugin>[];
}
@Module({})
export class PluginModule {
static forRoot(
options: PluginModuleOptions,
): DynamicModule {
const pluginProviders = options.plugins.map((plugin) => ({
provide: plugin,
useClass: plugin,
}));
const pluginRegistryProvider = {
provide: 'PLUGIN_REGISTRY',
useFactory: (...plugins: Plugin[]) => plugins,
inject: options.plugins,
};
return {
module: PluginModule,
providers: [
...pluginProviders,
pluginRegistryProvider,
PluginExecutor,
],
exports: [PluginExecutor],
};
}
}
// Plugin implementations
@Injectable()
export class ValidationPlugin implements Plugin {
name = 'validation';
async initialize(): Promise<void> {
console.log('Validation plugin initialized');
}
async execute(data: any): Promise<any> {
// Validate data
return data;
}
}
@Injectable()
export class TransformPlugin implements Plugin {
name = 'transform';
async initialize(): Promise<void> {
console.log('Transform plugin initialized');
}
async execute(data: any): Promise<any> {
// Transform data
return data;
}
}
// Plugin executor
@Injectable()
export class PluginExecutor {
constructor(
@Inject('PLUGIN_REGISTRY')
private plugins: Plugin[],
) {}
async executeAll(data: any): Promise<any> {
let result = data;
for (const plugin of this.plugins) {
result = await plugin.execute(result);
}
return result;
}
}
// Usage
@Module({
imports: [
PluginModule.forRoot({
plugins: [ValidationPlugin, TransformPlugin],
}),
],
})
export class AppModule {}typescript
import { Module, DynamicModule, Type } from '@nestjs/common';
export interface Plugin {
name: string;
initialize(): Promise<void>;
execute(data: any): Promise<any>;
}
export interface PluginModuleOptions {
plugins: Type<Plugin>[];
}
@Module({})
export class PluginModule {
static forRoot(
options: PluginModuleOptions,
): DynamicModule {
const pluginProviders = options.plugins.map((plugin) => ({
provide: plugin,
useClass: plugin,
}));
const pluginRegistryProvider = {
provide: 'PLUGIN_REGISTRY',
useFactory: (...plugins: Plugin[]) => plugins,
inject: options.plugins,
};
return {
module: PluginModule,
providers: [
...pluginProviders,
pluginRegistryProvider,
PluginExecutor,
],
exports: [PluginExecutor],
};
}
}
// 插件实现
@Injectable()
export class ValidationPlugin implements Plugin {
name = 'validation';
async initialize(): Promise<void> {
console.log('Validation plugin initialized');
}
async execute(data: any): Promise<any> {
// 验证数据
return data;
}
}
@Injectable()
export class TransformPlugin implements Plugin {
name = 'transform';
async initialize(): Promise<void> {
console.log('Transform plugin initialized');
}
async execute(data: any): Promise<any> {
// 转换数据
return data;
}
}
// 插件执行器
@Injectable()
export class PluginExecutor {
constructor(
@Inject('PLUGIN_REGISTRY')
private plugins: Plugin[],
) {}
async executeAll(data: any): Promise<any> {
let result = data;
for (const plugin of this.plugins) {
result = await plugin.execute(result);
}
return result;
}
}
// 使用方式
@Module({
imports: [
PluginModule.forRoot({
plugins: [ValidationPlugin, TransformPlugin],
}),
],
})
export class AppModule {}Conditional Provider Registration
条件提供者注册
typescript
import { Module, DynamicModule } from '@nestjs/common';
@Module({})
export class StorageModule {
static forRoot(): DynamicModule {
const providers = [];
// Conditional provider based on environment
if (process.env.STORAGE_TYPE === 's3') {
providers.push({
provide: 'STORAGE_SERVICE',
useClass: S3StorageService,
});
} else if (process.env.STORAGE_TYPE === 'gcs') {
providers.push({
provide: 'STORAGE_SERVICE',
useClass: GcsStorageService,
});
} else {
providers.push({
provide: 'STORAGE_SERVICE',
useClass: LocalStorageService,
});
}
// Conditional feature providers
if (process.env.ENABLE_COMPRESSION === 'true') {
providers.push(CompressionService);
}
if (process.env.ENABLE_ENCRYPTION === 'true') {
providers.push(EncryptionService);
}
return {
module: StorageModule,
providers,
exports: ['STORAGE_SERVICE'],
};
}
}typescript
import { Module, DynamicModule } from '@nestjs/common';
@Module({})
export class StorageModule {
static forRoot(): DynamicModule {
const providers = [];
// 根据环境条件注册提供者
if (process.env.STORAGE_TYPE === 's3') {
providers.push({
provide: 'STORAGE_SERVICE',
useClass: S3StorageService,
});
} else if (process.env.STORAGE_TYPE === 'gcs') {
providers.push({
provide: 'STORAGE_SERVICE',
useClass: GcsStorageService,
});
} else {
providers.push({
provide: 'STORAGE_SERVICE',
useClass: LocalStorageService,
});
}
// 条件注册功能提供者
if (process.env.ENABLE_COMPRESSION === 'true') {
providers.push(CompressionService);
}
if (process.env.ENABLE_ENCRYPTION === 'true') {
providers.push(EncryptionService);
}
return {
module: StorageModule,
providers,
exports: ['STORAGE_SERVICE'],
};
}
}Best Practices
最佳实践
-
Use constructor injection over property injection: Constructor injection makes dependencies explicit, ensures they're available when the class is instantiated, and works better with TypeScript's type system.
-
Prefer class-based providers for services: Class providers are more idiomatic in NestJS, provide better type safety, and integrate seamlessly with decorators like @Injectable().
-
Use factory providers for complex initialization: When providers need async initialization, depend on other services, or require conditional logic, factory providers offer the flexibility needed.
-
Avoid circular dependencies with forwardRef: While forwardRef() solves circular dependencies, it's better to restructure your modules to eliminate the circular reference entirely.
-
Keep modules focused and cohesive: Each module should represent a single feature or domain. This improves maintainability, makes testing easier, and enables better code organization.
-
Use dynamic modules for configurable features: When building reusable modules that need configuration, implement forRoot() and forRootAsync() methods to provide flexible initialization.
-
Leverage REQUEST scope only when needed: Request-scoped providers have performance overhead. Use them only when you truly need per-request state, like request context or tenant isolation.
-
Use symbol tokens for better type safety: Symbol tokens prevent naming conflicts and provide better IntelliSense support compared to string tokens.
-
Export only what's needed from modules: Keep module interfaces minimal by exporting only the providers that other modules need to use. This maintains encapsulation and reduces coupling.
-
Test providers in isolation: Write unit tests that mock dependencies to test providers in isolation. Use integration tests to verify the full dependency graph works correctly.
-
优先使用构造函数注入而非属性注入:构造函数注入使依赖关系显式化,确保类实例化时依赖已就绪,并且更适配TypeScript的类型系统。
-
服务优先使用基于类的提供者:基于类的提供者在NestJS中更符合惯用写法,提供更好的类型安全性,能与@Injectable()等装饰器无缝集成。
-
复杂初始化使用工厂提供者:当提供者需要异步初始化、依赖其他服务或需要条件逻辑时,工厂提供者能提供所需的灵活性。
-
使用forwardRef避免循环依赖:虽然forwardRef()能解决循环依赖问题,但更好的做法是重构模块以彻底消除循环引用。
-
保持模块聚焦且内聚:每个模块应代表单个功能或领域。这能提升可维护性,简化测试,优化代码组织。
-
可配置功能使用动态模块:构建需要配置的可复用模块时,实现forRoot()和forRootAsync()方法以提供灵活的初始化方式。
-
仅在必要时使用REQUEST作用域:请求作用域提供者存在性能开销。仅当确实需要每个请求的独立状态(如请求上下文或租户隔离)时才使用。
-
使用符号令牌提升类型安全性:符号令牌能避免命名冲突,相比字符串令牌提供更好的智能提示支持。
-
模块仅导出必要内容:通过仅导出其他模块需要使用的提供者,保持模块接口简洁,维护封装性并降低耦合度。
-
隔离测试提供者:编写单元测试时通过模拟依赖来隔离测试提供者。使用集成测试验证完整依赖图是否正常工作。
Common Pitfalls
常见陷阱
-
Circular dependency errors: Occurs when Module A imports Module B, and Module B imports Module A. Restructure your modules or use forwardRef() as a last resort.
-
REQUEST scope performance overhead: Request-scoped providers are created for every request, which adds memory and CPU overhead. All dependent providers also become request-scoped.
-
Not handling async provider initialization: Forgetting to use async/await in factory providers can lead to providers being injected before they're fully initialized.
-
Overusing global modules: Global modules are convenient but can lead to tight coupling. Use them sparingly for truly global services like logging and configuration.
-
Missing provider exports in modules: If a provider is not listed in the module's exports array, it won't be available to other modules that import it.
-
Token name conflicts: Using generic string tokens like 'config' or 'service' across multiple modules can cause conflicts. Use descriptive, namespaced tokens.
-
Memory leaks with REQUEST scope: Request-scoped providers that hold references to large objects or don't clean up resources can cause memory leaks over time.
-
Not cleaning up resources in onModuleDestroy: Providers that create connections, timers, or other resources should implement onModuleDestroy to clean up properly.
-
Tight coupling between modules: Importing too many modules or depending on internal implementation details creates tight coupling that makes refactoring difficult.
-
Missing @Injectable() decorator: Forgetting to add @Injectable() to a class that should be a provider results in runtime errors when NestJS tries to inject it.
-
循环依赖错误:当模块A导入模块B,同时模块B导入模块A时会发生。重构模块或作为最后手段使用forwardRef()。
-
REQUEST作用域性能开销:请求作用域提供者会为每个请求创建实例,增加内存和CPU开销。所有依赖的提供者也会变为请求作用域。
-
未处理异步提供者初始化:在工厂提供者中忘记使用async/await会导致提供者在完全初始化前就被注入。
-
过度使用全局模块:全局模块虽然方便,但会导致紧耦合。仅将其用于日志、配置等真正全局的服务。
-
模块中未导出提供者:如果提供者未列在模块的exports数组中,导入该模块的其他模块将无法使用它。
-
令牌名称冲突:在多个模块中使用通用字符串令牌(如'config'或'service')会导致冲突。使用描述性的、带命名空间的令牌。
-
REQUEST作用域导致内存泄漏:持有大对象引用或未清理资源的请求作用域提供者可能随时间推移导致内存泄漏。
-
未在onModuleDestroy中清理资源:创建连接、定时器或其他资源的提供者应实现onModuleDestroy以正确清理资源。
-
模块间紧耦合:导入过多模块或依赖内部实现细节会造成紧耦合,增加重构难度。
-
遗漏@Injectable()装饰器:忘记为应作为提供者的类添加@Injectable()装饰器会导致NestJS尝试注入时出现运行时错误。