Dependency Injection (DI) is a design pattern that allows a class to receive its dependencies from external sources rather than creating them itself. This can improve the modularity and maintainability of your applications by decoupling class dependencies from their implementations. NestJS, a progressive Node.js framework, makes extensive use of dependency injection out of the box. In this article, we’ll explore how DI works in NestJS and why it’s an essential part of building scalable and testable applications.
What Is Dependency Injection?
In traditional software design, classes often instantiate their dependencies directly. This approach can lead to tightly coupled and difficult-to-test code. Dependency Injection addresses this issue by allowing us to inject dependencies into a class from an external source — typically, a framework or a container.
How NestJS Handles Dependency Injection
NestJS utilizes its own dependency injection container, which makes it easy to manage the lifecycle of dependencies and their scopes. NestJS provides two decorators, @Injectable()
and @Inject()
, which are essential for working with DI.
@Injectable()
: Marks a class as a candidate for dependency injection.@Inject()
: Allows the injection of a specific provider into a class.
Setting Up Dependency Injection in NestJS
Let’s walk through an example to see how dependency injection works in NestJS.
Step 1: Create a Service
First, create a simple service using the @Injectable()
decorator. This service will be responsible for basic operations, in this case, providing a greeting.
// src/hello.service.ts
import { Injectable } from '@nestjs/common';
@Injectable()
export class HelloService {
getHello(): string {
return 'Hello, World!';
}
}
Step 2: Inject the Service into a Controller
Next, you need to inject the HelloService
into a controller so you can use its getHello()
method. Use the @Injectable()
and @Inject()
decorators to accomplish this.
// src/hello.controller.ts
import { Controller, Get } from '@nestjs/common';
import { HelloService } from './hello.service';
@Controller('hello')
export class HelloController {
constructor(private readonly helloService: HelloService) {}
@Get()
getHello(): string {
return this.helloService.getHello();
}
}
Step 3: Register the Service in a Module
Finally, register the HelloService
in a module, so that NestJS knows where to look for this provider.
// src/hello.module.ts
import { Module } from '@nestjs/common';
import { HelloController } from './hello.controller';
import { HelloService } from './hello.service';
@Module({
imports: [],
controllers: [HelloController],
providers: [HelloService],
})
export class HelloModule {}
Step 4: Add the Module to the Root Module
Ensure that your custom module is included in the root module to make it part of the application.
// src/app.module.ts
import { Module } from '@nestjs/common';
import { HelloModule } from './hello/hello.module';
@Module({
imports: [HelloModule],
controllers: [],
providers: [],
})
export class AppModule {}
Running the Application
With everything set up, you can run the application and test if the dependency injection works as expected.
npm run start
Visit http://localhost:3000/hello
in a browser or use a tool like curl
to see the output:
curl http://localhost:3000/hello
You should see:
Hello, World!
Conclusion
Dependency Injection is a powerful feature that can greatly enhance the maintainability and testability of your code. NestJS makes it straightforward to use DI with its intuitive decorators and module-based architecture. By following the steps in this article, you should have a good understanding of how DI works in NestJS and how to apply it to your projects.
Happy coding!