Skip to content
On this page

NestJS提供者

提供者(Providers)是NestJS的基本构建块,几乎所有的类都可以被视为提供者,如服务、仓库、工厂、助手等。提供者可以被注入到构造函数中,使得模块之间的依赖关系更加清晰和灵活。

提供者基础

服务提供者

使用@Injectable()装饰器标记提供者:

typescript
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: string): Cat {
    return this.cats.find(cat => cat.id === id);
  }

  update(id: string, cat: Cat): Cat {
    const index = this.cats.findIndex(c => c.id === id);
    if (index !== -1) {
      this.cats[index] = { ...this.cats[index], ...cat };
      return this.cats[index];
    }
    return null;
  }

  remove(id: string): boolean {
    const index = this.cats.findIndex(cat => cat.id === id);
    if (index !== -1) {
      this.cats.splice(index, 1);
      return true;
    }
    return false;
  }
}

在控制器中使用提供者

typescript
import { Controller, Get, Post, Put, Delete, Param, Body } from '@nestjs/common';
import { CatsService } from './cats.service';
import { Cat } from './interfaces/cat.interface';

@Controller('cats')
export class CatsController {
  constructor(private catsService: CatsService) {}

  @Post()
  async create(@Body() createCatDto: Cat) {
    this.catsService.create(createCatDto);
  }

  @Get()
  async findAll(): Promise<Cat[]> {
    return this.catsService.findAll();
  }

  @Get(':id')
  async findOne(@Param('id') id: string): Promise<Cat> {
    return this.catsService.findOne(id);
  }

  @Put(':id')
  async update(
    @Param('id') id: string,
    @Body() updateCatDto: Cat,
  ): Promise<Cat> {
    return this.catsService.update(id, updateCatDto);
  }

  @Delete(':id')
  async remove(@Param('id') id: string): Promise<boolean> {
    return this.catsService.remove(id);
  }
}

提供者类型

1. 值提供者 (Value Providers)

typescript
// 值提供者 - 直接提供一个值
export const configProvider = {
  provide: 'CONFIG',
  useValue: {
    port: process.env.PORT || 3000,
    host: process.env.HOST || 'localhost',
    database: {
      url: process.env.DATABASE_URL || 'localhost:5432',
      options: {
        ssl: process.env.DB_SSL === 'true',
      },
    },
  },
};

// 在模块中使用
@Module({
  providers: [configProvider],
  exports: [configProvider],
})
export class ConfigModule {}

// 在服务中注入
@Injectable()
export class DatabaseService {
  constructor(@Inject('CONFIG') private config: any) {}

  connect() {
    return `Connecting to ${this.config.database.url}`;
  }
}

2. 类提供者 (Class Providers)

typescript
// 开发环境配置服务
export class DevelopmentConfigService {
  get(key: string): string {
    return `dev_${process.env[key]}`;
  }

  isDevelopment(): boolean {
    return true;
  }
}

// 生产环境配置服务
export class ProductionConfigService {
  get(key: string): string {
    return process.env[key];
  }

  isDevelopment(): boolean {
    return false;
  }
}

// 类提供者
export const configServiceProvider = {
  provide: 'ConfigService',
  useClass: process.env.NODE_ENV === 'production' 
    ? ProductionConfigService 
    : DevelopmentConfigService,
};

// 使用
@Injectable()
export class SomeService {
  constructor(@Inject('ConfigService') private configService: any) {}
}

3. 工厂提供者 (Factory Providers)

typescript
import { ConfigService } from './config.service';

// 工厂提供者
export const databaseProvider = {
  provide: 'DATABASE_CONNECTION',
  useFactory: (configService: ConfigService) => {
    const dbConfig = {
      host: configService.get('DB_HOST'),
      port: configService.get('DB_PORT'),
      username: configService.get('DB_USERNAME'),
      password: configService.get('DB_PASSWORD'),
    };
    
    // 创建数据库连接
    return createConnection(dbConfig);
  },
  inject: [ConfigService], // 依赖注入
};

// 复杂工厂提供者
export const loggerProvider = {
  provide: 'LoggerService',
  useFactory: (configService: ConfigService, request: Request) => {
    const options = {
      level: configService.get('LOG_LEVEL') || 'info',
      format: configService.get('LOG_FORMAT') || 'json',
      transports: [
        new transports.Console(),
        new transports.File({ filename: 'combined.log' }),
      ],
    };
    
    // 为每个请求创建特定的logger实例
    return new Logger({
      ...options,
      metadata: {
        requestId: request.headers['x-request-id'],
        userAgent: request.headers['user-agent'],
      },
    });
  },
  inject: [ConfigService, REQUEST], // 注入请求作用域的服务
  scope: Scope.REQUEST, // 请求作用域
};

4. 别名提供者 (Alias Providers)

typescript
// 别名提供者 - 为现有提供者创建别名
export const loggerAliasProvider = {
  provide: 'CUSTOM_LOGGER',
  useExisting: 'LOGGER', // 指向现有的提供者
};

// 在模块中使用
@Module({
  providers: [
    {
      provide: 'LOGGER',
      useValue: new ConsoleLogger(),
    },
    loggerAliasProvider, // 现在 'CUSTOM_LOGGER' 也指向同一个实例
  ],
})
export class LoggerModule {}

作用域提供者

作用域类型

typescript
import { Injectable, Scope } from '@nestjs/common';

// 单例作用域 (默认) - 整个应用程序共享一个实例
@Injectable()
export class SingletonService {
  private count = 0;
  
  increment(): number {
    return ++this.count;
  }
}

// 请求作用域 - 每个请求创建一个新实例
@Injectable({ scope: Scope.REQUEST })
export class RequestScopedService {
  private readonly id = Math.random();
  
  getId(): string {
    return this.id.toString();
  }
}

// 过渡作用域 - 每次注入都创建新实例
@Injectable({ scope: Scope.TRANSIENT })
export class TransientService {
  private readonly id = Math.random();
  
  getId(): string {
    return this.id.toString();
  }
}

请求作用域的实际应用

typescript
import { Injectable, Scope, Inject } from '@nestjs/common';
import { REQUEST } from '@nestjs/core';
import { Request } from 'express';

@Injectable({ scope: Scope.REQUEST })
export class RequestContextService {
  private readonly requestId: string;
  private readonly user: any;

  constructor(@Inject(REQUEST) private request: Request) {
    this.requestId = this.generateRequestId();
    this.user = this.extractUserFromRequest();
  }

  private generateRequestId(): string {
    return this.request.headers['x-request-id'] as string || 
           Math.random().toString(36).substr(2, 9);
  }

  private extractUserFromRequest(): any {
    return this.request.user || null;
  }

  getRequestId(): string {
    return this.requestId;
  }

  getUser(): any {
    return this.user;
  }

  getIp(): string {
    return this.request.ip;
  }
}

自定义提供者配置

动态提供者

typescript
// 根据环境创建动态提供者
export function createConfigProvider(env: string) {
  if (env === 'production') {
    return {
      provide: 'ConfigService',
      useFactory: () => {
        return new ProductionConfigService();
      },
    };
  } else {
    return {
      provide: 'ConfigService',
      useClass: DevelopmentConfigService,
    };
  }
}

// 使用动态提供者
@Module({
  providers: [
    createConfigProvider(process.env.NODE_ENV),
  ],
})
export class AppModule {}

条件提供者

typescript
// 条件提供者
export function createCacheProvider(cacheType: string) {
  switch (cacheType) {
    case 'redis':
      return {
        provide: 'CACHE_SERVICE',
        useFactory: (redisClient: RedisClient) => {
          return new RedisCacheService(redisClient);
        },
        inject: [RedisClient],
      };
    case 'memory':
      return {
        provide: 'CACHE_SERVICE',
        useClass: MemoryCacheService,
      };
    default:
      return {
        provide: 'CACHE_SERVICE',
        useClass: NoopCacheService,
      };
  }
}

提供者生命周期

生命周期钩子

typescript
import { 
  Injectable, 
  Scope, 
  OnModuleInit, 
  OnModuleDestroy,
  OnApplicationBootstrap 
} from '@nestjs/common';

@Injectable()
export class DatabaseService implements OnModuleInit, OnModuleDestroy, OnApplicationBootstrap {
  private connection: any;

  async onModuleInit() {
    console.log('DatabaseService initialized');
    this.connection = await this.connectToDatabase();
  }

  async onApplicationBootstrap() {
    console.log('Application bootstrap complete');
    await this.performPostBootstrapTasks();
  }

  async onModuleDestroy() {
    console.log('DatabaseService destroyed');
    await this.connection.close();
  }

  private async connectToDatabase() {
    // 连接数据库的逻辑
    return { connected: true };
  }

  private async performPostBootstrapTasks() {
    // 启动后执行的任务
  }
}

// 请求作用域的生命周期
@Injectable({ scope: Scope.REQUEST })
export class RequestService implements OnModuleInit {
  async onModuleInit() {
    console.log('RequestService initialized for this request');
  }

  process() {
    return 'Processing request';
  }
}

提供者注册

在模块中注册提供者

typescript
import { Module } from '@nestjs/common';
import { CatsController } from './cats.controller';
import { CatsService } from './cats.service';
import { 
  configProvider, 
  databaseProvider,
  loggerProvider 
} from './providers';

@Module({
  controllers: [CatsController],
  providers: [
    CatsService,           // 类提供者
    configProvider,        // 值提供者
    databaseProvider,      // 工厂提供者
    loggerProvider,        // 工厂提供者(请求作用域)
    {
      provide: 'CUSTOM_SERVICE',
      useFactory: () => {
        return new CustomService();
      },
    },
  ],
  exports: [
    CatsService,          // 导出服务供其他模块使用
    'DATABASE_CONNECTION', // 导出自定义提供者
  ],
})
export class CatsModule {}

异步提供者

typescript
// 异步提供者
export const asyncDatabaseProvider = {
  provide: 'ASYNC_DATABASE_CONNECTION',
  useFactory: async (configService: ConfigService) => {
    const dbConfig = configService.getDatabaseConfig();
    // 异步创建连接
    return await createConnection(dbConfig);
  },
  inject: [ConfigService],
};

// 在模块中使用
@Module({
  providers: [asyncDatabaseProvider],
  exports: [asyncDatabaseProvider],
})
export class DatabaseModule {}

提供者最佳实践

1. 明确提供者职责

typescript
// 好的提供者设计 - 职责单一
@Injectable()
export class UserService {
  constructor(
    private databaseService: DatabaseService,
    private loggerService: LoggerService,
  ) {}

  async findAll(): Promise<User[]> {
    this.loggerService.log('Fetching all users');
    return await this.databaseService.findAll('users');
  }

  async create(userData: CreateUserDto): Promise<User> {
    this.loggerService.log('Creating user');
    return await this.databaseService.create('users', userData);
  }
}

// 避免提供者过于复杂
@Injectable()
export class MassiveService {
  // 避免在一个服务中处理过多逻辑
}

2. 合理使用作用域

typescript
// 单例服务 - 适用于无状态或共享状态
@Injectable()
export class CacheService {
  private cache = new Map();
  
  get(key: string) {
    return this.cache.get(key);
  }
  
  set(key: string, value: any) {
    this.cache.set(key, value);
  }
}

// 请求作用域 - 适用于需要请求特定状态的场景
@Injectable({ scope: Scope.REQUEST })
export class RequestLoggerService {
  private startTime: number;
  private readonly requestId: string;
  
  constructor(@Inject(REQUEST) private request: Request) {
    this.startTime = Date.now();
    this.requestId = request.headers['x-request-id'] as string;
  }
  
  log(message: string) {
    console.log(`[${this.requestId}] ${message}`);
  }
  
  getDuration(): number {
    return Date.now() - this.startTime;
  }
}

3. 类型安全的提供者

typescript
// 使用符号作为令牌以提高类型安全
export const DATABASE_CONNECTION = Symbol('DATABASE_CONNECTION');
export const CONFIG_OPTIONS = Symbol('CONFIG_OPTIONS');

@Injectable()
export class UserService {
  constructor(
    @Inject(DATABASE_CONNECTION) private connection: any,
    @Inject(CONFIG_OPTIONS) private options: any,
  ) {}
}

// 或使用字符串令牌接口
const PROVIDERS = {
  DATABASE_CONNECTION: 'DATABASE_CONNECTION',
  CONFIG_OPTIONS: 'CONFIG_OPTIONS',
} as const;

@Injectable()
export class UserService {
  constructor(
    @Inject(PROVIDERS.DATABASE_CONNECTION) private connection: any,
  ) {}
}

4. 提供者测试

typescript
// 测试提供者
describe('UserService', () => {
  let userService: UserService;
  let databaseService: DatabaseService;

  beforeEach(async () => {
    const module: TestingModule = await Test.createTestingModule({
      providers: [
        UserService,
        {
          provide: DatabaseService,
          useValue: {
            findAll: jest.fn().mockResolvedValue([{ id: 1, name: 'John' }]),
            create: jest.fn().mockResolvedValue({ id: 1, name: 'John' }),
          },
        },
      ],
    }).compile();

    userService = module.get<UserService>(UserService);
    databaseService = module.get<DatabaseService>(DatabaseService);
  });

  it('should find all users', async () => {
    const users = await userService.findAll();
    expect(users).toEqual([{ id: 1, name: 'John' }]);
  });
});

高级提供者模式

提供者工厂

typescript
// 提供者工厂模式
export class ProviderFactory {
  static createDatabaseProvider(type: 'mysql' | 'postgres' | 'mongodb') {
    switch (type) {
      case 'mysql':
        return {
          provide: 'DATABASE_CONNECTION',
          useFactory: () => new MysqlConnection(),
        };
      case 'postgres':
        return {
          provide: 'DATABASE_CONNECTION',
          useFactory: () => new PostgresConnection(),
        };
      case 'mongodb':
        return {
          provide: 'DATABASE_CONNECTION',
          useFactory: () => new MongoConnection(),
        };
    }
  }
}

// 使用工厂
@Module({
  providers: [
    ProviderFactory.createDatabaseProvider('postgres'),
  ],
})
export class DatabaseModule {}

提供者装饰器

typescript
// 自定义提供者装饰器
export function CustomProvider(name: string) {
  return Injectable()(
    class extends Provider {
      static [Symbol.toStringTag] = name;
    }
  );
}

@CustomProvider('CustomService')
export class MyCustomService {
  // 实现
}

通过合理使用NestJS的提供者系统,可以构建高度可测试、可维护和可扩展的应用程序。