NestJS Providers: The Core Building Blocks of Dependency Injection
Discover all you need to know about NestJS providers
NestJS is a powerful framework for building scalable and maintainable server-side applications. One of its key features is the robust dependency injection system, which revolves around the concept of providers. In this blog post, we'll dive deep into what providers are, how they work, and how you can use them to create clean, modular, and testable code in your NestJS applications.
What are Providers?
In NestJS, providers are the basic building blocks for dependency injection. They are responsible for creating and managing the instances of the classes they provide. Providers can be services, repositories, factories, or any other class that holds business logic or data access logic.
Types of Providers
NestJS supports various types of providers, each serving a specific purpose:
Class Providers: The most common type, where a class is used as a provider.
Value Providers: Provide a static value, useful for constants or configurations.
Factory Providers: Use a factory function to create the provider's value.
Existing Providers: Alias an existing provider, useful for creating different names for the same provider.
Class Providers
Class providers are the most straightforward and commonly used providers in NestJS. They are simply classes decorated with the @Injectable()
decorator.
Example: UserService
Let's create a simple UserService
that provides user-related functionalities.
import { Injectable } from '@nestjs/common';
@Injectable()
export class UserService {
private users = [
{ id: 1, name: 'John Doe' },
{ id: 2, name: 'Jane Doe' },
];
getUsers() {
return this.users;
}
getUserById(id: number) {
return this.users.find(user => user.id === id);
}
createUser(name: string) {
const newUser = { id: Date.now(), name };
this.users.push(newUser);
return newUser;
}
}
Usage in a Module
import { Module } from '@nestjs/common';
import { UserService } from './user.service';
import { UserController } from './user.controller';
@Module({
providers: [UserService],
controllers: [UserController],
exports: [UserService],
})
export class UserModule {}
Injecting into a Controller
import { Controller, Get, Param, Post, Body } from '@nestjs/common';
import { UserService } from './user.service';
@Controller('users')
export class UserController {
constructor(private readonly userService: UserService) {}
@Get()
getAllUsers() {
return this.userService.getUsers();
}
@Get(':id')
getUser(@Param('id') id: number) {
return this.userService.getUserById(id);
}
@Post()
createUser(@Body('name') name: string) {
return this.userService.createUser(name);
}
}
Value Providers
Value providers are useful when you need to provide a constant value. This is often used for configurations.
Example: Configuration
import { Module } from '@nestjs/common';
const CONFIG = {
database: 'my_database',
apiKey: '123456789',
};
@Module({
providers: [
{
provide: 'CONFIG',
useValue: CONFIG,
},
],
exports: ['CONFIG'],
})
export class ConfigModule {}
Here, CONFIG
is provided as a constant value that can be injected wherever needed.
Usage in a Service
import { Injectable, Inject } from '@nestjs/common';
@Injectable()
export class AppService {
constructor(@Inject('CONFIG') private config: any) {}
getDatabaseName(): string {
return this.config.database;
}
}
Factory Providers
Factory providers allow you to create a value dynamically using a factory function. This is useful when the value needs to be computed or when dependencies are required to create the value.
Example: Database Connection
import { Module } from '@nestjs/common';
@Module({
providers: [
{
provide: 'DATABASE_CONNECTION',
useFactory: async () => {
const connection = await createDatabaseConnection();
return connection;
},
},
],
exports: ['DATABASE_CONNECTION'],
})
export class DatabaseModule {}
async function createDatabaseConnection() {
// Simulating database connection creation
return {
connect: () => console.log('Connected to database'),
disconnect: () => console.log('Disconnected from database'),
};
}
In this example, the DATABASE_CONNECTION
is created dynamically using a factory function.
Usage in a Service
import { Injectable, Inject } from '@nestjs/common';
@Injectable()
export class AppService {
constructor(@Inject('DATABASE_CONNECTION') private dbConnection: any) {}
connectToDatabase() {
this.dbConnection.connect();
}
disconnectFromDatabase() {
this.dbConnection.disconnect();
}
}
Existing Providers
Existing providers allow you to alias an existing provider. This is useful when you want to provide the same instance under different tokens.
Example: Alias for Logging Service
import { Module, Injectable } from '@nestjs/common';
@Injectable()
export class LoggingService {
log(message: string) {
console.log(message);
}
}
@Module({
providers: [
LoggingService,
{
provide: 'Logger',
useExisting: LoggingService,
},
],
exports: ['Logger'],
})
export class LoggingModule {}
Usage in a Service
import { Injectable, Inject } from '@nestjs/common';
@Injectable()
export class AppService {
constructor(@Inject('Logger') private logger: any) {}
performAction() {
this.logger.log('Action performed');
}
}
Using Providers in Modules
To use a provider, you need to register it in a module. Modules in NestJS act as organizational units that group related providers, controllers, and other modules together.
Example: App Module
import { Module, Injectable } from '@nestjs/common';
@Injectable()
export class LoggingService {
log(message: string) {
console.log(message);
}
}
@Module({
providers: [
LoggingService,
{
provide: 'Logger',
useExisting: LoggingService,
},
],
exports: ['Logger'],
})
export class LoggingModule {}
Usage in a Service
import { Injectable, Inject } from '@nestjs/common';
@Injectable()
export class AppService {
constructor(@Inject('Logger') private logger: any) {}
performAction() {
this.logger.log('Action performed');
}
}
Putting all things together
Combining these examples, let's see how they can be structured within modules and used together.
App Module
import { Module } from '@nestjs/common';
import { UserModule } from './user/user.module';
import { ConfigModule } from './config/config.module';
import { DatabaseModule } from './database/database.module';
import { LoggingModule } from './logging/logging.module';
import { AppService } from './app.service';
import { AppController } from './app.controller';
@Module({
imports: [UserModule, ConfigModule, DatabaseModule, LoggingModule],
providers: [AppService],
controllers: [AppController],
})
export class AppModule {}
App Service
import { Injectable, Inject } from '@nestjs/common';
import { UserService } from './user/user.service';
@Injectable()
export class AppService {
constructor(
private readonly userService: UserService,
@Inject('CONFIG') private config: any,
@Inject('DATABASE_CONNECTION') private dbConnection: any,
@Inject('Logger') private logger: any,
) {}
performTask() {
this.logger.log('Performing task...');
const users = this.userService.getUsers();
this.dbConnection.connect();
console.log(`Database: ${this.config.database}`);
this.dbConnection.disconnect();
return users;
}
}
App Controller
import { Controller, Get } from '@nestjs/common';
import { AppService } from './app.service';
@Controller()
export class AppController {
constructor(private readonly appService: AppService) {}
@Get()
getData() {
return this.appService.performTask();
}
}
By using these various types of providers and organizing them into modules, you can create a highly modular and maintainable NestJS application. Each type of provider serves a specific purpose, allowing you to manage dependencies effectively and inject them wherever needed in your application.
Conclusion
Happy coding with NestJS!