6月4日 15:43
What is the role of NestJS providers and services?
Provider Concept
Providers are a core concept in NestJS. They are classes decorated with @Injectable() that can be injected into other classes. Providers can handle business logic, data access, integration with other services, etc.
Provider Types
- Services: Encapsulate business logic
- Repositories: Handle data access
- Factories: Create and configure objects
- Providers: Any class that can be injected
Services
Services are the most common provider type, used to encapsulate business logic and reusable functionality.
Basic Service Structure
typescriptimport { Injectable } from '@nestjs/common'; @Injectable() export class UsersService { private readonly users: any[] = []; create(user: any) { this.users.push(user); return user; } findAll() { return this.users; } findOne(id: number) { return this.users.find(user => user.id === id); } update(id: number, updateUserDto: any) { const user = this.findOne(id); if (user) { Object.assign(user, updateUserDto); return user; } return null; } remove(id: number) { const index = this.users.findIndex(user => user.id === id); if (index > -1) { this.users.splice(index, 1); return true; } return false; } }
Using Services in Controllers
typescriptimport { Controller, Get, Post, Body, Param, Put, Delete } from '@nestjs/common'; import { UsersService } from './users.service'; @Controller('users') export class UsersController { constructor(private readonly usersService: UsersService) {} @Post() create(@Body() createUserDto: any) { return this.usersService.create(createUserDto); } @Get() findAll() { return this.usersService.findAll(); } }
Registering Providers in Modules
Method 1: Direct Class Provision
typescriptimport { Module } from '@nestjs/common'; import { UsersController } from './users.controller'; import { UsersService } from './users.service'; @Module({ controllers: [UsersController], providers: [UsersService], }) export class UsersModule {}
Method 2: Using provide and useClass
typescript@Module({ providers: [ { provide: UsersService, useClass: UsersService, }, ], }) export class UsersModule {}
Method 3: Using provide and useValue
typescript@Module({ providers: [ { provide: 'API_KEY', useValue: 'your-api-key-here', }, ], }) export class AppModule {}
Method 4: Using provide and useFactory
typescript@Module({ providers: [ { provide: UsersService, useFactory: (userRepository: UserRepository) => { return new UsersService(userRepository); }, inject: [UserRepository], }, ], }) export class UsersModule {}
Method 5: Using provide and useExisting
typescript@Module({ providers: [ UsersService, { provide: 'USERS_SERVICE', useExisting: UsersService, }, ], }) export class UsersModule {}
Dependency Injection Tokens
Class as Token
typescriptconstructor(private readonly usersService: UsersService) {}
String as Token
typescriptconstructor(@Inject('API_KEY') private readonly apiKey: string) {}
Symbol as Token
typescriptexport const API_KEY = Symbol('API_KEY'); constructor(@Inject(API_KEY) private readonly apiKey: string) {}
Scope Configuration
Default Scope (Singleton)
typescript@Injectable() export class UsersService {}
Request Scope
typescriptimport { Scope } from '@nestjs/common'; @Injectable({ scope: Scope.REQUEST }) export class UsersService {}
Transient Scope
typescript@Injectable({ scope: Scope.TRANSIENT }) export class UsersService {}
Custom Providers
Async Providers
typescript@Module({ providers: [ { provide: 'ASYNC_CONNECTION', useFactory: async () => { const connection = await createConnection(); return connection; }, }, ], }) export class AppModule {}
Dynamic Modules
typescriptimport { DynamicModule, Module } from '@nestjs/common'; @Module({}) export class DatabaseModule { static register(options: DatabaseOptions): DynamicModule { return { module: DatabaseModule, providers: [ { provide: 'DATABASE_OPTIONS', useValue: options, }, DatabaseService, ], exports: [DatabaseService], }; } }
Best Practices
- Single Responsibility Principle: Each service should only be responsible for one functional domain
- Dependency Injection: Use constructor injection for dependencies
- Interface Segregation: Define clear interface contracts
- Avoid Circular Dependencies: Avoid circular dependencies between services during design
- Use DTOs: Use Data Transfer Objects to pass data
- Error Handling: Handle business logic errors in the service layer
- Testability: Write testable service code
- Documentation: Add clear documentation comments to services
Service Layer Design Patterns
1. Repository Pattern
typescript@Injectable() export class UserRepository { constructor(private readonly dataSource: DataSource) {} async findById(id: number) { return this.dataSource.getRepository(User).findOne({ where: { id } }); } async findAll() { return this.dataSource.getRepository(User).find(); } }
2. Service Pattern
typescript@Injectable() export class UsersService { constructor( private readonly userRepository: UserRepository, private readonly emailService: EmailService, ) {} async createUser(createUserDto: CreateUserDto) { const user = await this.userRepository.create(createUserDto); await this.emailService.sendWelcomeEmail(user.email); return user; } }
3. Factory Pattern
typescript@Injectable() export class UserFactory { createUser(data: any): User { return new User(data); } }
Summary
The NestJS provider and service system provides:
- Flexible dependency injection mechanism
- Multiple provider registration methods
- Clear code organization structure
- High testability
- Good maintainability
Mastering providers and services is the core of building NestJS applications, enabling developers to write loosely coupled, reusable, and testable code. By properly using providers and services, you can build well-structured, maintainable enterprise applications.