0
0
NestJSframework~15 mins

Dependency injection basics in NestJS - Deep Dive

Choose your learning style9 modes available
Overview - Dependency injection basics
What is it?
Dependency injection is a way to give parts of your program the things they need to work, instead of making them find or create those things themselves. In NestJS, it means letting the framework provide services or objects to your classes automatically. This helps keep your code clean and easy to change. It works by declaring what you need, and NestJS gives it to you when your class is created.
Why it matters
Without dependency injection, every part of your program would have to create or find its own tools, making the code messy and hard to change. Dependency injection solves this by managing these tools for you, so you can focus on what your code should do. This makes your app easier to test, update, and maintain, saving time and reducing bugs.
Where it fits
Before learning dependency injection, you should understand basic TypeScript classes and how NestJS modules and providers work. After mastering dependency injection, you can learn advanced topics like custom providers, scopes, and lifecycle hooks in NestJS.
Mental Model
Core Idea
Dependency injection means giving a class the things it needs from outside, instead of making the class create them itself.
Think of it like...
It's like a chef in a kitchen who doesn't grow or buy ingredients but gets them delivered ready to use, so the chef can focus on cooking.
┌───────────────┐       provides       ┌───────────────┐
│   Service A   │────────────────────▶│  Class B      │
│ (ingredient)  │                     │ (chef)        │
└───────────────┘                     └───────────────┘

Class B asks for Service A, and NestJS delivers it automatically.
Build-Up - 7 Steps
1
FoundationWhat is Dependency Injection
🤔
Concept: Dependency injection means giving a class the things it needs instead of making it create them.
In NestJS, classes often need other classes to work. Instead of creating those classes inside, NestJS can provide them automatically. This is called dependency injection. For example, a controller might need a service. Instead of creating the service, NestJS injects it.
Result
Classes receive their dependencies automatically, making code cleaner and easier to manage.
Understanding that dependencies come from outside helps you write simpler and more flexible code.
2
FoundationProviders and Injection Tokens
🤔
Concept: Providers are classes or values that NestJS can inject into other classes using tokens.
In NestJS, a provider is a class annotated with @Injectable(). NestJS uses the class type as a token to know what to inject. You register providers in modules, so NestJS knows how to create and share them.
Result
NestJS knows which classes to create and inject when needed.
Knowing providers are the building blocks of injection helps you organize your app's dependencies.
3
IntermediateConstructor Injection Pattern
🤔Before reading on: do you think dependencies are injected via method calls or constructor parameters? Commit to your answer.
Concept: NestJS injects dependencies by passing them as parameters to the class constructor.
When you add parameters to a class constructor and mark them with types of providers, NestJS automatically passes the right instances when creating the class. For example, a controller constructor can ask for a service, and NestJS injects it.
Result
Your class can use dependencies immediately after creation without extra setup.
Constructor injection makes dependencies explicit and ensures they are ready when the class starts.
4
IntermediateSingleton Scope of Providers
🤔Before reading on: do you think NestJS creates a new instance of a provider every time or shares one instance? Commit to your answer.
Concept: By default, providers are singletons, meaning one shared instance is used throughout the app.
When you register a provider, NestJS creates one instance and shares it wherever injected. This saves memory and keeps state consistent. You can change this behavior with scopes, but singleton is the default.
Result
All classes using the same provider get the same instance.
Understanding singleton scope helps avoid bugs from unexpected multiple instances.
5
IntermediateUsing Custom Providers
🤔Before reading on: do you think providers must always be classes? Commit to your answer.
Concept: Providers can be classes, values, or factories, allowing flexible ways to supply dependencies.
NestJS lets you define custom providers using useValue, useFactory, or useClass. This means you can inject simple values or create instances with custom logic. For example, injecting a config object or a database connection.
Result
You can inject almost anything, not just classes.
Knowing custom providers exist expands what you can inject and how you design your app.
6
AdvancedHierarchical Injection and Module Boundaries
🤔Before reading on: do you think all providers are shared globally or isolated per module? Commit to your answer.
Concept: NestJS supports hierarchical injection, where providers can be scoped to modules, controlling visibility and lifetime.
Modules can have their own providers that are only visible inside. If a provider is not exported, other modules can't use it. This helps organize large apps and avoid conflicts. NestJS resolves dependencies by searching up the module tree.
Result
You control which parts of your app share or isolate dependencies.
Understanding module boundaries prevents accidental sharing and helps design scalable apps.
7
ExpertInjection Tokens and Circular Dependencies
🤔Before reading on: do you think circular dependencies cause runtime errors or are handled automatically? Commit to your answer.
Concept: Injection tokens let you inject dependencies by string or symbol keys, and circular dependencies require special handling.
Sometimes classes depend on each other, causing circular references. NestJS can handle this with forwardRef() to delay resolution. Also, injection tokens let you inject interfaces or abstract classes by providing a unique key, enabling more flexible designs.
Result
You can manage complex dependency graphs and abstract injections safely.
Knowing how to handle circular dependencies and tokens is key for advanced, maintainable architectures.
Under the Hood
NestJS uses a container that keeps track of all providers registered in modules. When a class needs a dependency, NestJS looks up the provider by its token, creates an instance if needed, and passes it to the class constructor. This happens at runtime during app startup or when a module is loaded. The container manages scopes and lifetimes, ensuring singletons or new instances as configured.
Why designed this way?
This design separates creation from usage, following the inversion of control principle. It makes code modular and testable. Early frameworks mixed creation and use, causing tight coupling. NestJS builds on Angular's proven DI system, balancing flexibility and simplicity for server-side apps.
┌───────────────┐       registers       ┌───────────────┐
│   Module A    │────────────────────▶│  Provider X   │
└───────────────┘                      └───────────────┘
        │                                      ▲
        │ injects                             │
        ▼                                      │
┌───────────────┐                      ┌───────────────┐
│ Class B       │◀────────────────────│  Container    │
│ (needs X)    │       resolves        │ (stores X)    │
└───────────────┘                      └───────────────┘
Myth Busters - 4 Common Misconceptions
Quick: Do you think dependency injection means manually creating and passing dependencies everywhere? Commit to yes or no.
Common Belief:Dependency injection means you have to manually create and pass all dependencies yourself.
Tap to reveal reality
Reality:NestJS automatically creates and injects dependencies for you based on the providers registered in modules.
Why it matters:Believing this leads to writing extra boilerplate code and missing the benefits of automatic injection.
Quick: Do you think each injection creates a new instance of the provider? Commit to yes or no.
Common Belief:Every time a dependency is injected, a new instance is created.
Tap to reveal reality
Reality:By default, providers are singletons, so the same instance is shared across injections.
Why it matters:Misunderstanding this can cause bugs when expecting fresh instances or unexpected shared state.
Quick: Do you think dependency injection can solve all code design problems automatically? Commit to yes or no.
Common Belief:Using dependency injection automatically makes code well-designed and bug-free.
Tap to reveal reality
Reality:Dependency injection is a tool that helps organize code but does not replace good design or testing practices.
Why it matters:Overreliance on DI can lead to complex, hard-to-understand code if used without discipline.
Quick: Do you think circular dependencies are always handled automatically without errors? Commit to yes or no.
Common Belief:NestJS automatically resolves circular dependencies without any special setup.
Tap to reveal reality
Reality:Circular dependencies require explicit handling using forwardRef() to avoid runtime errors.
Why it matters:Ignoring this causes app crashes or unexpected behavior in production.
Expert Zone
1
Providers can have different scopes like transient or request-scoped, affecting instance lifetime and performance.
2
Using injection tokens allows injecting interfaces or abstract classes, enabling more flexible and testable designs.
3
Forward references (forwardRef) are essential to resolve circular dependencies but should be used sparingly to avoid complexity.
When NOT to use
Dependency injection is not ideal for very simple applications where adding DI adds unnecessary complexity. In such cases, direct instantiation or factory functions might be simpler. Also, avoid DI for static utility functions that do not maintain state.
Production Patterns
In real-world NestJS apps, DI is used to inject services like database clients, configuration, and logging. Modules are organized to encapsulate features with private providers. Custom providers and injection tokens enable swapping implementations for testing or different environments.
Connections
Inversion of Control (IoC)
Dependency injection is a specific way to implement IoC by handing control of dependencies to a container.
Understanding IoC clarifies why DI improves modularity and testability by reversing who creates dependencies.
Factory Design Pattern
Custom providers using useFactory relate to the factory pattern by creating objects through a method rather than direct construction.
Knowing factories helps understand how DI can create complex or dynamic dependencies.
Supply Chain Management
Dependency injection is like managing a supply chain where parts are delivered to assembly points just in time.
Seeing DI as supply chain logistics highlights the importance of timing, availability, and organization in software dependencies.
Common Pitfalls
#1Forgetting to register a provider in the module.
Wrong approach:constructor(private readonly myService: MyService) {} // MyService not in providers array
Correct approach:@Module({ providers: [MyService] }) export class MyModule {}
Root cause:NestJS cannot inject a provider it does not know about because it is not registered.
#2Creating circular dependencies without forwardRef.
Wrong approach:class A { constructor(private b: B) {} } class B { constructor(private a: A) {} }
Correct approach:class A { constructor(@Inject(forwardRef(() => B)) private b: B) {} } class B { constructor(private a: A) {} }
Root cause:NestJS needs forwardRef to resolve circular references at runtime.
#3Injecting a provider by interface without using injection token.
Wrong approach:constructor(private readonly config: ConfigInterface) {} // interface cannot be used as token
Correct approach:const CONFIG_TOKEN = 'CONFIG_TOKEN'; @Inject(CONFIG_TOKEN) private readonly config: ConfigInterface
Root cause:Interfaces do not exist at runtime, so tokens are needed to identify providers.
Key Takeaways
Dependency injection in NestJS automatically provides classes with the dependencies they need, improving code clarity and flexibility.
Providers are the core units registered in modules that NestJS uses to create and inject dependencies.
Constructor injection is the main pattern where dependencies are passed as constructor parameters.
By default, providers are singletons shared across the app, but scopes can be customized for different lifetimes.
Handling circular dependencies and using injection tokens are advanced techniques essential for building scalable and maintainable applications.