Bird
Raised Fist0
NestJSframework~15 mins

Dependency injection basics in NestJS - Deep Dive

Choose your learning style10 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.

Practice

(1/5)
1. What is the main purpose of dependency injection in NestJS?
easy
A. To automatically provide required services to classes
B. To manually create instances of services inside components
C. To write all code inside a single file
D. To avoid using classes and use only functions

Solution

  1. Step 1: Understand dependency injection concept

    Dependency injection means NestJS automatically gives the needed services to classes without manual creation.
  2. Step 2: Compare options with this concept

    Only To automatically provide required services to classes describes automatic service provision, others describe manual or unrelated actions.
  3. Final Answer:

    To automatically provide required services to classes -> Option A
  4. Quick Check:

    Dependency injection = automatic service provision [OK]
Hint: Dependency injection means NestJS gives services automatically [OK]
Common Mistakes:
  • Thinking you must create service instances manually
  • Confusing dependency injection with code organization only
  • Believing dependency injection removes classes
2. Which decorator marks a class as injectable for NestJS dependency injection?
easy
A. @Module()
B. @Controller()
C. @Injectable()
D. @Service()

Solution

  1. Step 1: Recall NestJS decorators for DI

    Classes that can be injected must be marked with @Injectable() decorator.
  2. Step 2: Check each option

    @Controller is for controllers, @Module for modules, @Service is not a NestJS decorator.
  3. Final Answer:

    @Injectable() -> Option C
  4. Quick Check:

    @Injectable() marks injectable classes [OK]
Hint: Use @Injectable() to mark services for injection [OK]
Common Mistakes:
  • Using @Controller or @Module instead of @Injectable
  • Assuming @Service is a valid NestJS decorator
  • Forgetting to add any decorator
3. Given this code snippet, what will be the output when appService.getMessage() is called?
import { Injectable } from '@nestjs/common';

@Injectable()
export class AppService {
  getMessage() {
    return 'Hello from AppService';
  }
}

export class AppController {
  constructor(private readonly appService: AppService) {}

  showMessage() {
    return this.appService.getMessage();
  }
}
medium
A. Error: AppService not provided
B. 'Hello from AppService'
C. undefined
D. 'Hello from AppController'

Solution

  1. Step 1: Understand injection in constructor

    AppController receives AppService instance via constructor injection because AppService is marked @Injectable.
  2. Step 2: Trace method call

    showMessage calls appService.getMessage(), which returns 'Hello from AppService'.
  3. Final Answer:

    'Hello from AppService' -> Option B
  4. Quick Check:

    Injected service method returns message [OK]
Hint: Injected service methods return their defined values [OK]
Common Mistakes:
  • Assuming injection fails without @Module setup
  • Expecting AppController message instead
  • Confusing method return values
4. Identify the error in this NestJS service injection code:
import { Injectable } from '@nestjs/common';

export class UserService {
  getUser() {
    return 'User data';
  }
}

@Injectable()
export class UserController {
  constructor(private readonly userService: UserService) {}

  fetchUser() {
    return this.userService.getUser();
  }
}
medium
A. UserService is missing @Injectable() decorator
B. UserController should not have a constructor
C. fetchUser method should be async
D. userService property should be public

Solution

  1. Step 1: Check decorators on injectable classes

    UserService is injected into UserController but lacks @Injectable(), so NestJS cannot inject it.
  2. Step 2: Verify other options

    Constructor in controller is valid, fetchUser need not be async, and private readonly is correct for injection.
  3. Final Answer:

    UserService is missing @Injectable() decorator -> Option A
  4. Quick Check:

    Injectable classes must have @Injectable() [OK]
Hint: Injectable services must have @Injectable() decorator [OK]
Common Mistakes:
  • Forgetting @Injectable on services
  • Thinking constructor is invalid in controllers
  • Assuming async is required for injected methods
5. You want to inject two different services, LoggerService and ConfigService, into a controller. Which is the correct way to do this in NestJS?
hard
A. Inject only one service and import the other directly
B. Create instances manually inside the controller constructor
C. Use @Injectable() on the controller and call services statically
D. Use constructor injection with both services as parameters marked private readonly

Solution

  1. Step 1: Recall how to inject multiple services

    In NestJS, multiple services are injected by listing them as constructor parameters with access modifiers like private readonly.
  2. Step 2: Evaluate options

    Use constructor injection with both services as parameters marked private readonly correctly uses constructor injection. Create instances manually inside the controller constructor manually creates instances, breaking DI. Use @Injectable() on the controller and call services statically misuses @Injectable on controller and static calls. Inject only one service and import the other directly injects only one service, which is incomplete.
  3. Final Answer:

    Use constructor injection with both services as parameters marked private readonly -> Option D
  4. Quick Check:

    Multiple services injected via constructor parameters [OK]
Hint: Inject multiple services by listing them in constructor [OK]
Common Mistakes:
  • Manually creating service instances inside controllers
  • Using static calls instead of injection
  • Injecting only one service when multiple are needed
Recommended

Ready to prove it?

Pass the test and this pattern moves unknown strong while earning +1 Grip toward your certificate.

10 questions~5 mineasy hard