Providers: Dependency Injection in NestJS
Understanding providers (services, repositories, factories, etc.), injecting dependencies, and the scope of providers.
Dependency Injection in NestJS
Introduction
Dependency Injection (DI) is a design pattern that promotes loose coupling between classes by providing dependencies to a class instead of hardcoding them within the class itself. NestJS, built on TypeScript, provides a robust and elegant DI system based on the Angular DI framework.
Core Concepts of Dependency Injection in NestJS
What is a Dependency?
In the context of DI, a dependency is an object that another object needs to function correctly. It's a service or component that a class relies on. For example, a UserService
might depend on a UserRepository
to persist user data.
The Injector
The NestJS injector is responsible for creating and managing dependencies. It maintains a container of registered providers (services, components, etc.) and resolves dependencies when they are needed. NestJS handles the injector automatically.
Providers
Providers are the objects that are managed by the NestJS injector. They can be services, repositories, factories, or any other value that can be injected into a class. You register providers in modules using the providers
array.
Injection
Injection is the process of providing a dependency to a class. NestJS uses constructor injection, meaning that dependencies are injected into the class constructor. You specify the dependencies you need using constructor parameters.
How Dependency Injection Works in NestJS
- Define a Provider: Create a class that represents the dependency you want to inject. Mark it as injectable using the
@Injectable()
decorator. - Register the Provider: Add the provider to the
providers
array in a module using the@Module()
decorator. - Inject the Dependency: In the class that needs the dependency, declare it as a constructor parameter. NestJS will automatically resolve and inject the dependency when the class is instantiated.
Example
Let's illustrate with a simple example. Assume we have a CatsService
that depends on a CatsRepository
to interact with a database.
// cats.service.ts
import { Injectable } from '@nestjs/common';
import { CatsRepository } from './cats.repository';
@Injectable()
export class CatsService {
constructor(private readonly catsRepository: CatsRepository) {}
async findAll(): Promise<Cat[]> {
return this.catsRepository.findAll();
}
async create(cat: Cat): Promise<Cat> {
return this.catsRepository.create(cat);
}
}
// cats.repository.ts
import { Injectable } from '@nestjs/common';
@Injectable()
export class CatsRepository {
async findAll(): Promise<Cat[]> {
// Logic to fetch cats from database
return [{ id: 1, name: 'Whiskers' }]; // Dummy data for example
}
async create(cat: Cat): Promise<Cat> {
// Logic to save cat to database
return cat; // Dummy data for example
}
}
// cats.module.ts
import { Module } from '@nestjs/common';
import { CatsController } from './cats.controller';
import { CatsService } from './cats.service';
import { CatsRepository } from './cats.repository';
@Module({
controllers: [CatsController],
providers: [CatsService, CatsRepository],
})
export class CatsModule {}
// cats.controller.ts
import { Controller, Get, Post, Body } from '@nestjs/common';
import { CatsService } from './cats.service';
import { Cat } from './interfaces/cat.interface';
import { CreateCatDto } from './dto/create-cat.dto';
@Controller('cats')
export class CatsController {
constructor(private readonly catsService: CatsService) {}
@Post()
async create(@Body() createCatDto: CreateCatDto): Promise<Cat> {
return this.catsService.create(createCatDto);
}
@Get()
async findAll(): Promise<Cat[]> {
return this.catsService.findAll();
}
}
Explanation:
@Injectable()
decorator marksCatsService
andCatsRepository
as providers.- The
CatsModule
registers bothCatsService
andCatsRepository
as providers in theproviders
array. - The
CatsService
constructor takesCatsRepository
as a parameter. NestJS automatically injects an instance ofCatsRepository
whenCatsService
is created. - The
CatsController
constructor takesCatsService
as a parameter. NestJS automatically injects an instance ofCatsService
whenCatsController
is created.
Benefits of Dependency Injection
- Loose Coupling: Classes are less dependent on specific implementations of their dependencies, making them easier to test and maintain.
- Modularity: You can easily swap out dependencies without modifying the classes that depend on them.
- Testability: You can easily mock or stub dependencies during testing, allowing you to isolate and test individual classes.
- Reusability: Dependencies can be reused in multiple classes.
- Maintainability: Code becomes more organized and easier to understand.
Conclusion
Dependency Injection is a fundamental concept in NestJS that promotes well-structured, maintainable, and testable applications. By understanding and utilizing DI, you can build robust and scalable NestJS applications with ease.