providers
NestJS Providers
When to Use This Skill
Use this skill when:
- Creating services for business logic
- Implementing dependency injection
- Working with repositories or data access layers
- Creating custom providers with factories or values
- Understanding provider scopes and lifecycles
- Injecting dependencies into controllers or other providers
What are Providers?
Providers are classes that can be injected as dependencies. They handle business logic, data access, and can be shared across the application through NestJS's IoC (Inversion of Control) container.
Basic Service
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);
}
}
Key Points:
@Injectable()decorator marks class as provider- Enables dependency injection
- Contains business logic separate from controllers
- Can be injected into other classes
Dependency Injection
Constructor Injection (Recommended)
import { Controller } from '@nestjs/common';
import { CatsService } from './cats.service';
@Controller('cats')
export class CatsController {
constructor(private readonly catsService: CatsService) {}
@Get()
findAll() {
return this.catsService.findAll();
}
}
Property Injection
import { Injectable, Inject } from '@nestjs/common';
@Injectable()
export class CatsService {
@Inject('CONNECTION')
private connection: Connection;
}
Use property injection only when:
- The class extends another class with constructor params
- You need to inject into a base class
Registering Providers
Providers must be registered in a module:
import { Module } from '@nestjs/common';
import { CatsController } from './cats.controller';
import { CatsService } from './cats.service';
@Module({
controllers: [CatsController],
providers: [CatsService],
})
export class CatsModule {}
Provider Patterns
Standard Provider (Class)
@Module({
providers: [CatsService], // Shorthand
})
// Equivalent to:
@Module({
providers: [
{
provide: CatsService,
useClass: CatsService,
},
],
})
Value Provider
Inject a constant value or mock object:
const mockCatsService = {
findAll: () => [],
};
@Module({
providers: [
{
provide: CatsService,
useValue: mockCatsService,
},
],
})
Use cases:
- Testing (mocking dependencies)
- Injecting configuration objects
- External libraries
Class Provider (Alternative Implementation)
class ConfigService {
// Base implementation
}
class DevelopmentConfigService extends ConfigService {
// Development-specific logic
}
@Module({
providers: [
{
provide: ConfigService,
useClass: DevelopmentConfigService,
},
],
})
Factory Provider
Create provider dynamically based on other dependencies:
@Module({
providers: [
{
provide: 'CONNECTION',
useFactory: async (config: ConfigService) => {
const connection = await createConnection(config.getDatabaseConfig());
return connection;
},
inject: [ConfigService],
},
],
})
Key Points:
useFactory- Function that returns the provider valueinject- Array of dependencies to inject into factory- Can be async
- Perfect for conditional logic or async initialization
Alias Provider (Existing)
Create an alias for an existing provider:
@Module({
providers: [
ConfigService,
{
provide: 'AliasedConfigService',
useExisting: ConfigService,
},
],
})
Custom Provider Tokens
String Tokens
@Module({
providers: [
{
provide: 'API_KEY',
useValue: 'my-secret-key',
},
],
})
// Inject with @Inject()
@Injectable()
export class ApiService {
constructor(@Inject('API_KEY') private apiKey: string) {}
}
Symbol Tokens
export const API_KEY = Symbol('API_KEY');
@Module({
providers: [
{
provide: API_KEY,
useValue: 'my-secret-key',
},
],
})
@Injectable()
export class ApiService {
constructor(@Inject(API_KEY) private apiKey: string) {}
}
Optional Dependencies
import { Injectable, Optional, Inject } from '@nestjs/common';
@Injectable()
export class HttpService {
constructor(
@Optional()
@Inject('HTTP_OPTIONS')
private httpOptions: HttpOptions
) {
if (!httpOptions) {
this.httpOptions = defaultOptions;
}
}
}
Provider Scopes
Default (Singleton)
Provider instance shared across entire application:
@Injectable()
export class CatsService {}
Request Scope
New instance created for each request:
import { Injectable, Scope } from '@nestjs/common';
@Injectable({ scope: Scope.REQUEST })
export class CatsService {}
Use when:
- Need request-specific data
- Multi-tenancy per request
- Request tracking
Performance note: Request-scoped providers have performance implications
Transient Scope
New instance created each time it's injected:
import { Injectable, Scope } from '@nestjs/common';
@Injectable({ scope: Scope.TRANSIENT })
export class CatsService {}
Exporting Providers
Make providers available to other modules:
@Module({
providers: [CatsService],
exports: [CatsService], // Now available to importing modules
})
export class CatsModule {}
// In another module
@Module({
imports: [CatsModule],
controllers: [DogsController], // Can inject CatsService
})
export class DogsModule {}
Async Providers
For providers requiring async initialization:
@Module({
providers: [
{
provide: 'ASYNC_CONNECTION',
useFactory: async () => {
const connection = await createConnection();
return connection;
},
},
],
})
Complete Example
// database.providers.ts
export const databaseProviders = [
{
provide: 'DATABASE_CONNECTION',
useFactory: async (config: ConfigService) => {
const connection = await createConnection({
host: config.get('DB_HOST'),
port: config.get('DB_PORT'),
});
return connection;
},
inject: [ConfigService],
},
];
// cats.repository.ts
@Injectable()
export class CatsRepository {
constructor(
@Inject('DATABASE_CONNECTION')
private connection: Connection,
) {}
async findAll(): Promise<Cat[]> {
return this.connection.query('SELECT * FROM cats');
}
async create(cat: CreateCatDto): Promise<Cat> {
return this.connection.insert('cats', cat);
}
}
// cats.service.ts
@Injectable()
export class CatsService {
constructor(
private readonly catsRepository: CatsRepository,
private readonly logger: Logger,
) {}
async findAll(): Promise<Cat[]> {
this.logger.log('Finding all cats');
return this.catsRepository.findAll();
}
async create(createCatDto: CreateCatDto): Promise<Cat> {
this.logger.log('Creating a cat');
return this.catsRepository.create(createCatDto);
}
}
// cats.module.ts
@Module({
providers: [
...databaseProviders,
CatsRepository,
CatsService,
Logger,
],
controllers: [CatsController],
exports: [CatsService], // Available to other modules
})
export class CatsModule {}
Circular Dependencies
Use forwardRef() to resolve circular dependencies:
// cats.service.ts
@Injectable()
export class CatsService {
constructor(
@Inject(forwardRef(() => DogsService))
private dogsService: DogsService,
) {}
}
// dogs.service.ts
@Injectable()
export class DogsService {
constructor(
@Inject(forwardRef(() => CatsService))
private catsService: CatsService,
) {}
}
Best Practices
- Single Responsibility - Each service should have one clear purpose
- Use constructor injection - Cleaner than property injection
- Prefer singleton scope - Unless you specifically need request/transient
- Export selectively - Only export what other modules need
- Use interfaces - Define contracts for better testing
- Avoid circular dependencies - Refactor to a shared service if needed
- Use factories for complex initialization - Especially with async operations
- Type your providers - Leverage TypeScript for type safety
- Keep services testable - Inject all dependencies
- Use descriptive names -
UserService,AuthService, notService1
Common Patterns
Repository Pattern
@Injectable()
export class UserRepository {
constructor(
@InjectRepository(User)
private userRepository: Repository<User>,
) {}
async findById(id: string): Promise<User> {
return this.userRepository.findOne({ where: { id } });
}
}
Service Layer Pattern
@Injectable()
export class UserService {
constructor(
private readonly userRepository: UserRepository,
private readonly emailService: EmailService,
) {}
async createUser(dto: CreateUserDto): Promise<User> {
const user = await this.userRepository.create(dto);
await this.emailService.sendWelcomeEmail(user.email);
return user;
}
}
Factory Pattern
@Injectable()
export class CatFactory {
create(type: string): Cat {
switch (type) {
case 'persian':
return new PersianCat();
case 'siamese':
return new SiameseCat();
default:
return new Cat();
}
}
}
Testing Providers
describe('CatsService', () => {
let service: CatsService;
let repository: CatsRepository;
beforeEach(async () => {
const module: TestingModule = await Test.createTestingModule({
providers: [
CatsService,
{
provide: CatsRepository,
useValue: {
findAll: jest.fn(),
create: jest.fn(),
},
},
],
}).compile();
service = module.get<CatsService>(CatsService);
repository = module.get<CatsRepository>(CatsRepository);
});
it('should be defined', () => {
expect(service).toBeDefined();
});
});