Files
Portfolio_Site/content/posts/provider-and-de-in-nestjs.md
SauravDhakal efb76f222a
All checks were successful
ci/woodpecker/push/woodpecker Pipeline was successful
post: Added provider and dependency injection one
2026-09-28 21:16:49 +05:45

134 lines
3.9 KiB
Markdown
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
---
title: "Understanding Providers and Dependency Injection in NestJS"
date: 2026-09-28T18:50:00+05:45
draft: false
tags: ["nestjs", "backend"]
description: "Concept of dependency injection in NestJS"
---
When I first started working with NestJS, registering providers (and controllers) inside a module felt like nothing more than a chore.
It was one of those things I did because *“that’s just how Nest is designed.”*
Even after I learned that modules exist to build an application graph, which it uses internally to resolve relationships and dependencies between modules and providers, the idea still felt vague and abstract. I understood **what** was happening, but not really **why** it worked the way it did.
Recently, I decided to take a deeper dive into how providers are registered and how Nest actually injects them. It turned out to be far more interesting than I expected.
Here’s what I learned.
## First, let’s understand what Dependency Injection is.
Wikipedia defines Dependency Injection(DI) as:
> **Dependency Injection** is a programming technique in which an object or function receives other objects or functions that it requires, as opposed to creating them internally.
Let’s look at a simple example.
Imagine a restaurant application. When a user’s order is ready for pickup, a notification needs to be sent.
```tsx
class OrderService {
// code operating over orders
const notificationService = new NotificationService()
notificationService.notify(userId, message)
// ...
}
```
Here, `OrderService` handles order-related logic, and `NotificationService` handles notifications.
However, because `NotificationService` is instantiated directly inside `OrderService` using `new`, this **is not** dependency injection.
Now compare that with this:
```tsx
class OrderService {
constructor(
private readonly notificationService: NotificationService,
) {}
// code operating over orders
notificationService.notify(userId, message)
// ...
}
```
This is dependency injection.
`OrderService` no longer cares about **how** `NotificationService` is created. It simply declares that it needs one, and something else provides it. That instance can also be cached and reused across every class that needs it, if configuration is same.
## Why Dependency Injection?
Dependency Injection helps by:
- Reducing coupling between classes
- Making testing easier (you can inject mock dependencies)
- Encouraging modular, maintainable code
## How Dependency Injection Works in NestJS
In NestJS, the basic flow looks something like this:
1. Define a provider using `@Injectable()`
2. Register that provider with Nest
3. Ask Nest to inject it using constructor-based injection
Registration usually happens in a module:
```tsx
@Module({
providers: [DemoService],
controllers: [DemoController],
})
export class DemoModule {}
```
At this point, Nest becomes responsible for creating and managing instances of `DemoService`.
## Nest’s IoC Container (The Missing Piece)
NestJS uses an **IoC (Inversion of Control) container** to manage dependencies.
You can think of this container as a **key-value** **registry**:
- **Keys** are called *tokens*
- **Values** describe how to create or retrieve a dependency
When we write:
```tsx
providers: [DemoService]
```
Nest internally expands it into a standard provider definition:
```tsx
providers: [
{
provide: DemoService,
useClass: DemoService,
},
]
```
In this case:
- The **token** is `DemoService` (the class itself)
- `useClass` tells Nest which class to instantiate when that token is requested.
So when Nest encounters:
```tsx
constructor(private readonly demoService: DemoService) {}
```
1. Nest looks up the `DemoService` token in the IoC container
2. It finds the matching provider record
3. It resolves the provider (`useClass` in this case)
4. The instance is created (or reused, since providers are singletons by default)
5. The resolved instance is injected into the class