Dependency Injection

⭐ Interview Importance: MEDIUM
⏱️ Revision Time: 8 min

Dependency Injection (DI) is an Inversion of Control (IoC) technique wherein you delegate the instantiation of dependencies to an external container (the Nest runtime).

Overview

NestJS has a built-in IoC container that resolves relationships between providers. This is the mechanism that allows you to write clean, modular, and highly testable code.

Instead of a class creating its own dependencies (using new SomeService()), it asks the framework to provide them. This decouples classes from their concrete implementations.

Key Concepts

  • IoC Container: The core engine in NestJS that instantiates classes and resolves their dependencies automatically.
  • Constructor Injection: The most common way to inject a dependency. You declare it in the constructor signature, and Nest automatically assigns it to a property.
  • Tokens: By default, Nest uses the class name/type as the injection token. You can also use string or symbol tokens for custom providers.
  • Resolution Process: When a class is instantiated, Nest looks at its constructor arguments, finds the corresponding providers in the Module, instantiates them (if they aren’t already instantiated singletons), and passes them in.

Code Examples

Standard Constructor Injection

import { Injectable, Controller } from '@nestjs/common';

@Injectable()
class ConfigService {
  getApiKey() { return 'secret-123'; }
}

@Controller()
class AppController {
  // Nest analyzes the type (ConfigService) and injects the singleton instance automatically.
  // The 'private readonly' shorthand assigns it to 'this.configService'.
  constructor(private readonly configService: ConfigService) {}

  getHello() {
    return this.configService.getApiKey();
  }
}

Property-based Injection

While constructor injection is preferred, sometimes you have deeply nested inheritance trees. Using property injection can avoid massive super() calls in subclasses.

import { Injectable, Inject } from '@nestjs/common';

@Injectable()
export class HttpService<T> {
  // Inject using the @Inject decorator on a property
  @Inject('HTTP_CLIENT_TOKEN')
  private readonly httpClient: HttpClient;
}

Injecting Custom Providers (String Tokens)

If you register a provider using a string token, you must use the @Inject() decorator to retrieve it, because TypeScript interfaces don’t exist at runtime.

// In the module: { provide: 'DATABASE_CONNECTION', useValue: connection }

@Injectable()
export class UsersRepository {
  constructor(
    @Inject('DATABASE_CONNECTION') private connection: any,
  ) {}
}

Best Practices

  • Prefer Constructor Injection: It makes the dependencies of a class explicit and ensures the class cannot be instantiated without its required dependencies, which is great for unit testing.
  • Interface Segregation: Don’t inject massive “God Services” into every controller. Inject only the specific services that provide the functionality needed.
  • Leverage Singletons: By default, DI creates Singletons. This is highly performant and shares state effectively. Only use request-scoped providers when absolutely necessary (e.g., for multi-tenancy based on the request user).