| name | nestjs-dependency-injection |
| user-invocable | false |
| description | Use when nestJS dependency injection with providers, modules, and decorators. Use when building modular NestJS applications. |
| allowed-tools | ["Bash","Read","Write","Edit","Glob","Grep"] |
NestJS Dependency Injection
Master NestJS dependency injection for building modular, testable
Node.js applications with proper service architecture, provider
patterns, and module organization.
Table of Contents
Provider Patterns
Class Providers (Standard Pattern)
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
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 (): <> {
response = (.., {
: ..,
});
response.();
}
}
Factory Providers
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: [
,
,
,
],
: [, ],
})
{}
Async Providers with useFactory
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 = ({
: config.(),
});
client.();
client.(, {
.(, err);
});
client;
},
: [],
};
({
: [, ],
: [, ],
})
{}
Token-Based Injection with String Tokens
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 {}
()
{
() {}
(: ): <> {
..(, { : data. });
..();
user = ..(data);
user;
}
}
Token-Based Injection with Symbol Tokens
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 {}
()
{
() {}
(: ): <> {
cached = ..();
(cached) cached;
product = .
.()
.({ : { id } });
..(, product, );
product;
}
}
Optional Dependencies with @Optional()
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);
}
.(user, message);
}
(
: ,
: ,
): <> {
.();
}
}
({
: [
,
],
: [],
})
{}
Property-Based Injection
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()
: ,
()
: ,
) {}
(: ): <> {
..(data.);
..(
data.,
data.,
);
..(data);
}
}
Class Provider with useClass
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): {
fs.(, );
}
(: , : ): {
fs.(, );
}
}
({
: [
{
: ,
:
process.. ===
?
: ,
},
],
: [],
})
{}
()
{
() {}
(): {
..();
}
}
Alias Providers (useExisting)
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.();
.(users);
}
}
Module System
Module Organization and Encapsulation
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()
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,
) {}
}
@Module({
imports: [
CoreModule,
FeatureModule1,
FeatureModule2,
],
})
export class AppModule {}
Dynamic Modules with forRoot
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,
};
}
}
({
: [
.({
: ,
: ,
: ,
: ,
: ,
}),
],
})
{}
Dynamic Modules with forRootAsync
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 (cacheOptions);
},
: [],
};
{
: ,
: [
cacheOptionsProvider,
cacheProvider,
,
],
: [, ],
: ,
};
}
}
({
: [
.(),
.({
: (: ) => ({
: config.(),
: config.(),
: config.(),
}),
: [],
}),
],
})
{}
Module Re-exporting
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 {}
@Module({
imports: [SharedModule],
providers: [UserService],
controllers: [],
})
{}
({
: [],
: [],
: [],
})
{}
Circular Dependencies Handling
import { Module, forwardRef } from '@nestjs/common';
@Module({
imports: [forwardRef(() => AuthModule)],
providers: [UserService],
exports: [UserService],
})
export class 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
import { Module } from '@nestjs/common';
@Module({
imports: [SharedModule],
providers: [
AdminService,
AdminGuard,
],
controllers: [AdminController],
})
export class AdminModule {}
@Module({
imports: [
CoreModule,
UserModule,
],
})
export class AppModule {}
@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.();
}
}
Module Configuration Pattern
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({
: ,
: ,
: ,
: ,
}),
],
})
{}
({
: [.()],
: [],
})
{}
Shared Module Pattern
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 {}
@Module({
imports: [
UtilsModule,
DataAccessModule,
],
exports: [
UtilsModule,
DataAccessModule,
],
})
export class SharedModule {}
Injection Scopes
DEFAULT Scope (Singleton)
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>();
(: , : ): {
..(key, value);
}
(: ): {
..(key);
}
(): {
..();
}
}
REQUEST Scope with Performance Implications
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;
}
}
({ : . })
{
() {}
(: ): <> {
..({
: ..(),
: ..(),
action,
: (),
});
}
}
({ : . })
{
() {
.();
}
(: ): <> {
user = ..(data);
..();
user;
}
}
TRANSIENT Scope
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,
) {}
}
()
{
() {}
}
({ : . })
{
: [] = [];
: [] = [];
(: , ...: []): {
..(condition);
..(...params);
;
}
(): { : ; : [] } {
{
: ,
: .,
};
}
}
Durable Providers
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 {
: [] = [];
(: ): {
..(message);
}
(): [] {
.;
}
}
Scope Inheritance
import { Injectable, Scope } from '@nestjs/common';
@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) {}
(): {
..[] ;
}
}
({ : . })
{
() {}
(): <[]> {
tenantId = ..();
..(
,
);
}
}
Scope Configuration in Modules
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
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<>,
() : ,
) {}
(): <[]> {
..();
cached = ..();
(cached) cached;
users = ..();
..(, users, );
users;
}
}
({
: [
{
: ,
: (),
},
{
: ,
: ,
},
{
: ,
: (),
},
],
})
{}
Provider Arrays and Multi-Providers
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);
}
}
({
: [
,
,
,
{
: ,
: [userHandler, auditHandler, notificationHandler],
: [
,
,
,
],
},
],
: [],
})
{}
()
{
() {}
(: ): {
..( {
handler.(event);
});
}
}
Lazy Module Loading
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> {
const moduleRef = ..(
()
.( m.),
);
pdfService = moduleRef.();
pdfService.(report);
}
}
Testing with Dependency Injection
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 = .();
});
(, () => {
users = [{ : , : }];
jest.(repository, ).(users);
result = service.();
(result).(users);
(repository.).();
});
(, () => {
createDto = { : , : };
user = { : , ...createDto };
jest.(repository, ).(user);
jest.(repository, ).(user);
result = service.(createDto);
(result).(user);
(repository.).(createDto);
(repository.).(user);
});
});
(, {
: ;
: ;
( () => {
moduleRef = .({
: [
.({
: ,
: ,
: ,
: ,
}),
,
],
}).();
app = moduleRef.();
app.();
service = moduleRef.<>();
});
( () => {
app.();
});
(, () => {
createDto = { : , : };
user = service.(createDto);
(user.).();
(user.).(createDto.);
found = service.(user.);
(found).(user);
});
});
ModuleRef for Dynamic Provider Resolution
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: });
} {
..();
}
}
}
({ : . })
{
() {}
(): <> {
context = ..(
,
);
userId = context.();
userService = ..();
userService.(userId);
}
}
Plugin Pattern with Dependency Injection
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,
};
{
: ,
: [
...pluginProviders,
pluginRegistryProvider,
,
],
: [],
};
}
}
()
{
name = ;
(): <> {
.();
}
(: ): <> {
data;
}
}
()
{
name = ;
(): <> {
.();
}
(: ): <> {
data;
}
}
()
{
() {}
(: ): <> {
result = data;
( plugin .) {
result = plugin.(result);
}
result;
}
}
({
: [
.({
: [, ],
}),
],
})
{}
Conditional Provider Registration
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 === ) {
providers.();
}
{
: ,
providers,
: [],
};
}
}
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.
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.
Resources