The Dependency Injection Container

How Nest Wires Your Objects Together

The Dependency Injection Container

Nest's DI container constructs your providers and wires them via constructor injection, so you never write `new` for your services.

4 min read Level 2/5 #nestjs#di#container
What you'll learn
  • Understand what dependency injection does for you
  • Use constructor injection in providers and controllers
  • Distinguish between class references and string tokens

Dependency injection (DI) is the heart of Nest. Instead of building objects yourself, you describe what you need; the framework constructs and hands them to you.

What DI Actually Does

Without DI, wiring an app means manually new-ing every dependency in the right order:

// Manual wiring — fine for tiny apps, painful at scale
const repo = new UsersRepository(db);
const mailer = new Mailer(smtpConfig);
const service = new UsersService(repo, mailer);
const controller = new UsersController(service);

With Nest, you declare classes as @Injectable() and ask for them in your constructor. The container inspects the metadata, builds a dependency graph, and constructs everything for you.

@Injectable()
export class UsersService {
  constructor(
    private readonly repo: UsersRepository,
    private readonly mailer: Mailer,
  ) {}
}

Constructor Injection

Nest only supports constructor injection for required dependencies. TypeScript’s private readonly shortcut declares the field and assigns it in one go.

@Controller('users')
export class UsersController {
  // `users` is now a field on the instance, set by Nest
  constructor(private readonly users: UsersService) {}

  @Get()
  list() {
    return this.users.findAll();
  }
}

You don’t call new UsersService() anywhere. The module registers it; Nest takes care of the rest.

Tokens vs Class References

Internally, every provider is keyed by a token. When you write UsersService as the type, the class itself is the token — Nest reads its constructor types via emitDecoratorMetadata and looks each up.

For non-class values (strings, plain config, interfaces) you need an explicit token, usually a string or symbol:

// Provider definition (in a module)
providers: [{ provide: 'API_KEY', useValue: process.env.API_KEY }];

// Injecting it — types alone can't identify a string, so you tell Nest
@Injectable()
export class StripeService {
  constructor(@Inject('API_KEY') private readonly key: string) {}
}

By-class is just a shortcut. Under the hood it’s the same lookup — a token maps to a provider definition, and the container builds it.

Services — The Business Layer →