0
0
NestJSframework~15 mins

Custom providers in NestJS - Deep Dive

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