04. Dependency Injection & IoC
1. The Vanilla Mechanics
Dependency Injection (DI) is a design pattern where an object’s dependencies are provided by an external source rather than created by the object itself. In plain Node.js or Express, this often leads to “Constructor Hell” or complex manual instantiation.
Concept: Manual Instantiation
- Tightly Coupled: Creating a
new DatabaseService()inside aUserService. - Testing: Difficulty in mocking dependencies for unit tests.
The “Raw” Implementation (Example)
class DatabaseService {
query(sql: string) { /* ... */ }
}
class UserService {
private db: DatabaseService;
constructor(db: DatabaseService) {
this.db = db; // Injected manually
}
getUsers() { return this.db.query('SELECT * FROM users'); }
}
// Manual bootstrapping:
const db = new DatabaseService();
const userService = new UserService(db); // Manual DIChallenges:
- Scalability: As the app grows, manual DI becomes unmanageable.
- Singleton Management: Ensuring a single instance of a service across the entire app is difficult without a container.
- Circular Dependencies: Manually resolving circular dependencies is almost impossible.
2. The NestJS Abstraction
NestJS uses an Inversion of Control (IoC) container to manage your application’s dependencies.
Key Advantages:
@Injectable(): Marks a class as a provider that can be managed by the IoC container.- Constructor Injection: Simply declaring a dependency in the constructor is enough for NestJS to resolve and inject it.
- Singletons by Default: NestJS manages service lifecycles and creates singletons automatically.
The NestJS Implementation:
@Injectable()
export class DatabaseService { /* ... */ }
@Injectable()
export class UserService {
// NestJS automatically injects DatabaseService here!
constructor(private db: DatabaseService) {}
getUsers() { return this.db.findAll(); }
}
// In the module:
@Module({
providers: [DatabaseService, UserService], // Register with the IoC container
})
export class UsersModule {}3. Engineering Labs
- Lab 4.1: Build a simple CLI app with a
Logger, aRepository, and aService. Instantiate them manually in amainscript. - Lab 4.2: Re-implement the same CLI using the NestJS standalone application mode. Use
@Injectable()and observe how NestJS resolves the dependency graph.