Skip to content

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 a UserService.
  • 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 DI

Challenges:

  • 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, a Repository, and a Service. Instantiate them manually in a main script.
  • Lab 4.2: Re-implement the same CLI using the NestJS standalone application mode. Use @Injectable() and observe how NestJS resolves the dependency graph.