Bird
Raised Fist0
NestJSframework~15 mins

Custom providers in NestJS - Deep Dive

Choose your learning style10 modes available
Overview - Custom providers
What is it?
Custom providers in NestJS are a way to tell the framework how to create or supply a specific value or service. Instead of using the default class-based providers, you can define your own logic to provide dependencies. This helps you control exactly what gets injected into your components, like services or controllers.
Why it matters
Without custom providers, you would be limited to only using classes as services, which can be inflexible. Custom providers let you inject values, functions, or even complex objects created with special logic. This flexibility is crucial for adapting to different needs, like mocking services in tests or integrating third-party libraries.
Where it fits
Before learning custom providers, you should understand basic dependency injection and how NestJS uses providers and modules. After mastering custom providers, you can explore advanced dependency injection patterns, dynamic modules, and testing strategies in NestJS.
Mental Model
Core Idea
A custom provider is a recipe you give NestJS to create or supply a value or service exactly how you want it.
Think of it like...
Imagine you have a coffee machine that usually makes coffee the same way every time. A custom provider is like giving the machine a special recipe card so it can make a new kind of coffee or even tea, exactly how you want.
┌───────────────────────────┐
│       NestJS Module       │
├─────────────┬─────────────┤
│  Providers  │  Consumers  │
│             │             │
│ ┌─────────┐ │ ┌─────────┐ │
│ │ Default │ │ │ Service │ │
│ │Provider │ │ │ or Ctrl │ │
│ └─────────┘ │ └─────────┘ │
│             │             │
│ ┌───────────────┐         │
│ │ Custom        │         │
│ │ Provider      │────────▶│
│ │ (your recipe) │         │
│ └───────────────┘         │
└───────────────────────────┘
Build-Up - 7 Steps
1
FoundationUnderstanding NestJS Providers
🤔
Concept: Learn what providers are and how NestJS uses them to inject dependencies.
In NestJS, providers are classes that can be injected into other classes like controllers or services. They are the building blocks for dependency injection. When you declare a class as a provider, NestJS creates an instance and shares it where needed.
Result
You understand that providers are the way NestJS manages and shares services across your app.
Knowing providers are the core of NestJS's dependency injection helps you see why customizing them matters.
2
FoundationBasic Dependency Injection Setup
🤔
Concept: Learn how to register and inject a simple provider in a module.
You create a service class, add it to the providers array of a module, and inject it into a controller via the constructor. NestJS automatically creates and shares the service instance.
Result
Your controller can use the service without manually creating it.
Understanding this automatic wiring is key to appreciating how custom providers change the rules.
3
IntermediateWhat Are Custom Providers?
🤔Before reading on: do you think custom providers only replace classes or can they provide values and functions too? Commit to your answer.
Concept: Custom providers let you define exactly how NestJS creates or supplies a dependency, not just by class.
Instead of just listing a class, you can provide an object with keys like 'provide' and 'useValue', 'useClass', or 'useFactory'. This tells NestJS to inject a specific value, a different class, or a factory function result.
Result
You can inject constants, functions, or customized instances, not just default classes.
Knowing you can control the injection source expands what you can do with NestJS beyond simple classes.
4
IntermediateUsing useValue and useClass Providers
🤔Before reading on: which do you think is better for injecting a constant value, useValue or useClass? Commit to your answer.
Concept: Learn the difference between injecting a fixed value and injecting a class instance under a custom token.
useValue lets you inject a fixed value or object directly. useClass lets you tell NestJS to instantiate a different class than the token name suggests. For example, you can inject a mock class for testing.
Result
You can inject constants or swap implementations easily.
Understanding these options helps you write flexible and testable code.
5
IntermediateUsing useFactory for Dynamic Providers
🤔Before reading on: do you think useFactory can receive other injected dependencies? Commit to your answer.
Concept: useFactory lets you provide a function that creates the value, possibly using other injected providers.
You define a factory function that returns the value to inject. You can also specify dependencies that NestJS injects into the factory function. This is useful for creating complex or async objects.
Result
You can create providers that depend on other providers or runtime data.
Knowing factories can receive dependencies unlocks powerful dynamic injection patterns.
6
AdvancedCustom Providers with Async Factories
🤔Before reading on: can you guess how to handle async initialization in a custom provider? Commit to your answer.
Concept: You can create providers whose factory functions return promises, allowing async setup before injection.
By marking the factory function as async and returning a promise, NestJS waits for the promise to resolve before injecting the value. This is useful for things like database connections or config loading.
Result
Your app can inject fully initialized async resources seamlessly.
Understanding async factories lets you integrate external async services cleanly.
7
ExpertToken Injection and Provider Scopes
🤔Before reading on: do you think custom provider tokens must be strings? Commit to your answer.
Concept: Custom providers use tokens to identify what to inject. Tokens can be strings, symbols, or classes. Also, providers can have different scopes affecting lifecycle.
Tokens are keys NestJS uses to match injection requests. Using symbols avoids naming collisions. Provider scopes like singleton or request-scoped control instance sharing. Custom providers respect these scopes, affecting performance and behavior.
Result
You can design providers with precise identity and lifecycle control.
Knowing tokens and scopes deeply helps avoid bugs and optimize resource use in complex apps.
Under the Hood
NestJS maintains a container that maps tokens to provider definitions. When a component requests a dependency, NestJS looks up the token and uses the provider's definition to create or retrieve the instance. Custom providers extend this by allowing the provider definition to specify how to create or supply the instance, whether by returning a fixed value, instantiating a class, or calling a factory function. This happens during the module initialization phase, ensuring dependencies are ready before use.
Why designed this way?
NestJS was designed to be flexible and modular, inspired by Angular's dependency injection. Custom providers allow developers to adapt the injection system to many use cases, like integrating third-party libraries or testing. The design balances simplicity for common cases (class providers) with power for advanced scenarios (factories, values). Alternatives like hard-coded dependencies or manual wiring were rejected because they reduce testability and modularity.
┌───────────────────────────────┐
│       NestJS Container        │
├─────────────┬─────────────────┤
│ Token Map   │ Provider Config │
│             │                 │
│ 'MyService' │ { useClass: A } │
│ 'Config'    │ { useValue: {} }│
│ 'Factory'   │ { useFactory: f }│
└──────┬──────┴─────────┬───────┘
       │                │
       ▼                ▼
┌───────────────┐  ┌───────────────┐
│ Create Class  │  │ Call Factory  │
│ Instance      │  │ Function      │
└──────┬────────┘  └──────┬────────┘
       │                 │
       ▼                 ▼
┌────────────────────────────────┐
│ Inject Instance or Value to     │
│ Requesting Component            │
└────────────────────────────────┘
Myth Busters - 4 Common Misconceptions
Quick: Do you think useValue providers create new instances each injection? Commit yes or no.
Common Belief:useValue providers create a new instance every time they are injected.
Tap to reveal reality
Reality:useValue providers always inject the exact same value or object instance wherever used.
Why it matters:Assuming new instances are created can lead to bugs when mutable objects are shared unexpectedly.
Quick: Do you think useFactory functions cannot receive other injected dependencies? Commit yes or no.
Common Belief:Factory functions cannot receive other providers as arguments.
Tap to reveal reality
Reality:Factory functions can declare dependencies that NestJS injects automatically.
Why it matters:Not knowing this limits the ability to create complex providers that depend on other services.
Quick: Do you think custom provider tokens must always be strings? Commit yes or no.
Common Belief:Provider tokens must be strings.
Tap to reveal reality
Reality:Tokens can be strings, symbols, or classes, allowing better uniqueness and type safety.
Why it matters:Using only strings can cause naming collisions and harder-to-maintain code.
Quick: Do you think async factory providers block the entire app startup? Commit yes or no.
Common Belief:Async factory providers delay the whole app startup until they finish.
Tap to reveal reality
Reality:NestJS waits only for the async provider to resolve before injecting it, not blocking unrelated parts.
Why it matters:Understanding this helps design efficient startup flows and avoid unnecessary delays.
Expert Zone
1
Custom providers can be combined with scopes like transient or request-scoped to finely control instance lifetimes, which is crucial for performance and correctness in web apps.
2
Using symbols as tokens prevents accidental collisions in large codebases or libraries, a subtle but important practice for maintainability.
3
Async factory providers can be used to lazily initialize resources only when needed, improving startup time and resource usage.
When NOT to use
Avoid custom providers when simple class providers suffice, as they add complexity. For very dynamic or runtime-dependent injections, consider dynamic modules or external configuration management instead.
Production Patterns
In production, custom providers are often used to inject configuration objects, mock services during testing, or wrap third-party libraries with factory functions. Scoped custom providers help manage per-request data in web applications.
Connections
Dependency Injection (General)
Custom providers are a specific implementation of dependency injection patterns.
Understanding custom providers deepens your grasp of how dependency injection frameworks can be customized and extended.
Factory Design Pattern
useFactory providers implement the factory pattern to create objects.
Recognizing this connection helps you apply well-known design principles within NestJS.
Supply Chain Management
Custom providers act like suppliers deciding how and when to deliver goods (services) to consumers.
Seeing providers as suppliers clarifies the importance of controlling creation and delivery in complex systems.
Common Pitfalls
#1Injecting a value provider but expecting a new instance each time.
Wrong approach:providers: [{ provide: 'CONFIG', useValue: { url: 'http://api' } }] constructor(@Inject('CONFIG') private config1, @Inject('CONFIG') private config2) { console.log(config1 === config2); // expecting false }
Correct approach:providers: [{ provide: 'CONFIG', useFactory: () => ({ url: 'http://api' }) }] constructor(@Inject('CONFIG') private config1, @Inject('CONFIG') private config2) { console.log(config1 === config2); // true because singleton }
Root cause:Misunderstanding that useValue injects the same object reference everywhere.
#2Trying to inject dependencies into a factory without declaring them in 'inject' array.
Wrong approach:providers: [{ provide: 'SERVICE', useFactory: (config) => new Service(config), }] // No 'inject' array specified
Correct approach:providers: [{ provide: 'SERVICE', useFactory: (config) => new Service(config), inject: ['CONFIG'], }]
Root cause:Not telling NestJS which dependencies to inject into the factory function.
#3Using string tokens that collide in large apps.
Wrong approach:providers: [{ provide: 'LOGGER', useClass: LoggerService }] // Another module also uses 'LOGGER' token
Correct approach:const LOGGER_TOKEN = Symbol('LOGGER'); providers: [{ provide: LOGGER_TOKEN, useClass: LoggerService }]
Root cause:Using plain strings as tokens without uniqueness leads to collisions.
Key Takeaways
Custom providers let you control exactly how NestJS creates or supplies dependencies beyond simple classes.
You can inject fixed values, swap classes, or use factory functions, including async ones, for flexible dependency management.
Tokens identifying providers can be strings, symbols, or classes, affecting uniqueness and maintainability.
Understanding provider scopes and tokens is key to writing scalable and bug-free NestJS applications.
Custom providers enable powerful patterns like mocking, configuration injection, and integration with external libraries.

Practice

(1/5)
1. What is the main purpose of a custom provider in NestJS?
easy
A. To manage database connections automatically
B. To define routes for HTTP requests
C. To control how NestJS creates or finds a service or value
D. To handle user authentication and authorization

Solution

  1. Step 1: Understand the role of providers in NestJS

    Providers are responsible for creating and managing services or values that can be injected into other parts of the app.
  2. Step 2: Identify what custom providers do

    Custom providers let you control exactly how NestJS creates or finds these services or values, making your app more flexible.
  3. Final Answer:

    To control how NestJS creates or finds a service or value -> Option C
  4. Quick Check:

    Custom provider purpose = control creation/finding [OK]
Hint: Custom providers control service creation or retrieval [OK]
Common Mistakes:
  • Confusing providers with routing or middleware
  • Thinking providers manage database or auth directly
  • Assuming providers only register classes, not values or factories
2. Which of the following is the correct syntax to provide a custom value provider in a NestJS module?
easy
A. { provide: 'API_URL', useValue: 'https://api.example.com' }
B. { provide: 'API_URL', useClass: 'https://api.example.com' }
C. { provide: 'API_URL', useFactory: 'https://api.example.com' }
D. { provide: 'API_URL', useExisting: 'https://api.example.com' }

Solution

  1. Step 1: Recall the syntax for value providers

    Value providers use the useValue key to assign a fixed value to a token.
  2. Step 2: Check each option's correctness

    { provide: 'API_URL', useValue: 'https://api.example.com' } correctly uses useValue with a string value. Other options misuse useClass, useFactory, or useExisting which expect different types.
  3. Final Answer:

    { provide: 'API_URL', useValue: 'https://api.example.com' } -> Option A
  4. Quick Check:

    Value provider uses useValue = { provide: 'API_URL', useValue: 'https://api.example.com' } [OK]
Hint: Value providers always use 'useValue' key [OK]
Common Mistakes:
  • Using useClass or useFactory for fixed values
  • Confusing useExisting with useValue
  • Putting string directly without useValue
3. Given the following provider configuration in a NestJS module:
{ provide: 'RANDOM_NUMBER', useFactory: () => Math.floor(Math.random() * 10) }
What will be the output if you inject 'RANDOM_NUMBER' twice in the same request?
medium
A. The same random number both times
B. Always zero because Math.random() is called once
C. An error because useFactory cannot be used this way
D. Two different random numbers between 0 and 9

Solution

  1. Step 1: Understand provider scope and factory behavior

    By default, NestJS providers are singletons per application context, so the factory runs once and the result is reused.
  2. Step 2: Analyze injection behavior

    Injecting 'RANDOM_NUMBER' twice in the same request returns the same value because the factory was called once at provider creation.
  3. Final Answer:

    The same random number both times -> Option A
  4. Quick Check:

    Singleton factory = same value injected [OK]
Hint: Default providers are singletons; factory runs once [OK]
Common Mistakes:
  • Assuming factory runs on every injection
  • Thinking useFactory causes errors without async
  • Believing random number changes per injection
4. Consider this provider configuration:
{ provide: 'CONFIG', useFactory: () => ({ port: 3000 }) }
If you want to inject this provider but get an error "Nest can't resolve dependencies", what is the most likely cause?
medium
A. The token 'CONFIG' must be a class, not a string
B. The useFactory function must be async
C. You cannot use useFactory with object literals
D. The provider is missing from the module's providers array

Solution

  1. Step 1: Check provider registration

    For NestJS to inject a provider, it must be registered in the module's providers array.
  2. Step 2: Evaluate other options

    useFactory can be sync or async, tokens can be strings, and useFactory can return objects. The error usually means the provider is not registered.
  3. Final Answer:

    The provider is missing from the module's providers array -> Option D
  4. Quick Check:

    Missing provider registration = injection error [OK]
Hint: Always register providers in module's providers array [OK]
Common Mistakes:
  • Assuming useFactory must be async
  • Thinking tokens must be classes only
  • Believing useFactory can't return objects
5. You want to create a custom provider that returns different database connection strings based on environment variables. Which approach correctly implements this using a factory provider in NestJS?
hard
A. { provide: 'DB_CONNECTION', useValue: process.env.NODE_ENV === 'prod' ? 'prod-db' : 'dev-db' }
B. { provide: 'DB_CONNECTION', useFactory: () => process.env.NODE_ENV === 'prod' ? 'prod-db' : 'dev-db' }
C. { provide: 'DB_CONNECTION', useClass: process.env.NODE_ENV === 'prod' ? ProdDbService : DevDbService }
D. { provide: 'DB_CONNECTION', useExisting: process.env.NODE_ENV === 'prod' ? 'ProdDbService' : 'DevDbService' }

Solution

  1. Step 1: Understand dynamic value creation

    To choose a value based on environment variables at runtime, a factory provider is ideal because it runs code to decide the value.
  2. Step 2: Evaluate each option

    { provide: 'DB_CONNECTION', useFactory: () => process.env.NODE_ENV === 'prod' ? 'prod-db' : 'dev-db' } uses useFactory with a function that returns the correct string based on process.env.NODE_ENV. { provide: 'DB_CONNECTION', useValue: process.env.NODE_ENV === 'prod' ? 'prod-db' : 'dev-db' } uses useValue which is static at module load time. Options C and D misuse useClass and useExisting with strings or conditions incorrectly.
  3. Final Answer:

    { provide: 'DB_CONNECTION', useFactory: () => process.env.NODE_ENV === 'prod' ? 'prod-db' : 'dev-db' } -> Option B
  4. Quick Check:

    Dynamic values need useFactory with runtime logic [OK]
Hint: Use useFactory for runtime-dependent values [OK]
Common Mistakes:
  • Using useValue for dynamic environment-based values
  • Misusing useClass or useExisting with strings
  • Expecting useFactory to be static
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