0
0
NestJSframework~15 mins

Provider scope (default, request, transient) in NestJS - Deep Dive

Choose your learning style9 modes available
Overview - Provider scope (default, request, transient)
What is it?
In NestJS, provider scope controls how and when instances of services or classes are created and shared. There are three main scopes: default (singleton), request, and transient. Default scope means one instance is shared across the whole app. Request scope creates a new instance for each incoming request. Transient scope creates a new instance every time the provider is injected.
Why it matters
Provider scopes help manage resource usage and data isolation in applications. Without scopes, all services would be singletons, which can cause bugs when state is shared unexpectedly or when you need fresh data per request. Scopes allow developers to control lifecycle and memory, improving app reliability and performance.
Where it fits
Before learning provider scopes, you should understand basic NestJS providers and dependency injection. After mastering scopes, you can explore advanced topics like custom providers, lifecycle hooks, and request context management.
Mental Model
Core Idea
Provider scope defines how often and when NestJS creates new instances of a service to control sharing and isolation.
Think of it like...
Imagine a coffee machine in an office: default scope is like one shared coffee pot everyone drinks from; request scope is like making a fresh cup for each visitor; transient scope is like making a new cup every time someone asks, even if it’s the same person.
┌───────────────┐       ┌───────────────┐       ┌───────────────┐
│ Default Scope │──────▶│ Request Scope │──────▶│ Transient Scope│
│ (Singleton)   │       │ (Per Request) │       │ (Every Inject)│
└───────────────┘       └───────────────┘       └───────────────┘
Build-Up - 7 Steps
1
FoundationWhat is a Provider in NestJS
🤔
Concept: Introduces the idea of providers as classes or services that NestJS creates and injects where needed.
In NestJS, a provider is a class annotated with @Injectable() that can be injected into other classes. Providers hold business logic or data access code. By default, NestJS creates one instance of each provider and shares it across the app.
Result
You understand that providers are the building blocks of NestJS apps and that by default, one instance is shared everywhere.
Understanding providers as shared instances is key to grasping why scopes matter for controlling instance creation.
2
FoundationDefault Scope: Singleton Providers
🤔
Concept: Explains the default behavior where one instance of a provider is reused throughout the app.
When you create a provider without specifying scope, NestJS treats it as a singleton. This means the same instance is injected everywhere. This is efficient and works well for stateless services or shared resources.
Result
Your app uses one instance of the provider, saving memory and ensuring consistent state if needed.
Knowing that default scope means singleton helps you predict when state is shared and when it might cause issues.
3
IntermediateRequest Scope: New Instance Per Request
🤔Before reading on: do you think request scope creates one instance per user session or per HTTP request? Commit to your answer.
Concept: Request scope creates a new provider instance for each incoming HTTP request, isolating data per request.
By setting scope: Scope.REQUEST on a provider, NestJS creates a fresh instance for every HTTP request. This is useful when providers hold request-specific data, like user info or request metadata, preventing data leaks between requests.
Result
Each HTTP request gets its own provider instance, isolating state and improving safety.
Understanding request scope prevents bugs caused by shared mutable state in web apps handling many users.
4
IntermediateTransient Scope: New Instance Every Injection
🤔Before reading on: does transient scope create one instance per request or per injection? Commit to your answer.
Concept: Transient scope creates a new instance every time the provider is injected, even multiple times in the same request.
When a provider is marked with scope: Scope.TRANSIENT, NestJS does not reuse instances. Every injection point gets a fresh instance. This is useful for lightweight, stateless services or when you want complete isolation even within the same request.
Result
Multiple injections of the same provider produce separate instances, avoiding any shared state.
Knowing transient scope helps design services that must never share state, even within a single request.
5
IntermediateHow to Set Provider Scope in NestJS
🤔
Concept: Shows the syntax and decorator usage to define provider scopes.
You set scope by adding the 'scope' property in the @Injectable() decorator: @Injectable({ scope: Scope.REQUEST }) export class MyService {} or @Injectable({ scope: Scope.TRANSIENT }) export class MyService {} Default scope is used if omitted.
Result
You can control provider lifecycle explicitly by setting scope in the decorator.
Knowing the syntax empowers you to apply scopes correctly and avoid accidental singleton sharing.
6
AdvancedImpact of Scopes on Dependency Injection Tree
🤔Before reading on: do you think a transient provider injected into a singleton stays transient or becomes singleton? Commit to your answer.
Concept: Explains how scopes affect nested dependencies and instance sharing in the injection graph.
When a provider with a narrower scope (like transient or request) is injected into a wider scope provider (like singleton), NestJS creates separate instances per injection. But if a singleton injects a request-scoped provider, the request-scoped provider behaves like a singleton, which can cause bugs. To avoid this, request-scoped providers should not be injected into singletons directly.
Result
You understand how scope boundaries affect instance lifetimes and sharing in complex apps.
Knowing scope interactions prevents subtle bugs where scoped providers behave unexpectedly due to injection hierarchy.
7
ExpertPerformance and Memory Considerations of Scopes
🤔Before reading on: do you think using request or transient scopes always improves safety without cost? Commit to your answer.
Concept: Discusses the tradeoffs of using different scopes on app performance and memory usage.
Request and transient scopes create many instances, increasing memory use and garbage collection. Overusing them can slow down your app. Singleton scope is efficient but risks shared mutable state bugs. Experts balance scopes by using singleton for stateless services and request/transient only when isolation is essential.
Result
You can make informed decisions balancing safety and performance in provider scope choices.
Understanding the cost of scopes helps design scalable, maintainable NestJS applications.
Under the Hood
NestJS uses a dependency injection container that manages provider instances. When a provider is requested, the container checks its scope. For default scope, it returns the same instance stored in a global map. For request scope, it creates a new instance per HTTP request context, tracked internally. For transient scope, it creates a new instance every time the provider is injected, never caching it. This is implemented using internal metadata and context-aware factories.
Why designed this way?
The design balances efficiency and flexibility. Singleton scope minimizes resource use by reusing instances. Request scope isolates data per user interaction, essential for web apps. Transient scope offers maximum isolation for cases needing fresh instances every time. Alternatives like always singleton or always new instance were rejected because they either waste resources or cause data leaks.
┌─────────────────────────────┐
│ NestJS Dependency Injector  │
├─────────────┬───────────────┤
│ Provider    │ Scope Check   │
├─────────────┼───────────────┤
│ Default     │ Return cached │
│ (Singleton) │ instance      │
├─────────────┼───────────────┤
│ Request     │ Create new    │
│             │ instance per  │
│             │ HTTP request  │
├─────────────┼───────────────┤
│ Transient   │ Create new    │
│             │ instance every│
│             │ injection     │
└─────────────┴───────────────┘
Myth Busters - 4 Common Misconceptions
Quick: Does request scope create one instance per user session or per HTTP request? Commit to your answer.
Common Belief:Request scope creates one instance per user session, so data is shared across multiple requests from the same user.
Tap to reveal reality
Reality:Request scope creates a new instance for every HTTP request, not per user session. Each request is isolated.
Why it matters:Believing request scope is per session can cause developers to store user session data in providers incorrectly, leading to bugs and data leaks.
Quick: Does transient scope reuse instances if injected multiple times in the same class? Commit to your answer.
Common Belief:Transient scope reuses the same instance within the same request or class injection.
Tap to reveal reality
Reality:Transient scope creates a new instance every time it is injected, even multiple times in the same request or class.
Why it matters:Misunderstanding this leads to unexpected behavior where state is not shared as expected, causing bugs in logic relying on shared instances.
Quick: If a singleton provider injects a request-scoped provider, does the request-scoped provider behave as request-scoped? Commit to your answer.
Common Belief:The request-scoped provider keeps its request scope behavior regardless of where it is injected.
Tap to reveal reality
Reality:When injected into a singleton, the request-scoped provider behaves like a singleton, losing request isolation.
Why it matters:This can cause serious bugs where request-specific data leaks across requests, breaking security and correctness.
Quick: Does using request or transient scope always improve app safety without downsides? Commit to your answer.
Common Belief:Using request or transient scope is always better because it isolates data and prevents bugs.
Tap to reveal reality
Reality:These scopes increase memory use and reduce performance due to many instances being created and destroyed.
Why it matters:Overusing these scopes can degrade app performance and scalability, so they must be used judiciously.
Expert Zone
1
Request-scoped providers require an active context; using them outside HTTP requests (like in background jobs) can cause errors.
2
Transient providers can cause subtle bugs if injected multiple times unintentionally, leading to inconsistent state within the same operation.
3
Combining scopes in complex dependency trees requires careful design to avoid scope mismatches and unexpected singleton promotion.
When NOT to use
Avoid request or transient scopes for heavy or long-lived services to prevent memory bloat. Use singleton scope for stateless or shared services. For per-user session data, use external session stores or context management instead of request scope.
Production Patterns
In production, singleton scope is the default for most services. Request scope is used for services handling user-specific data like authentication or request logging. Transient scope is rare but useful for lightweight helpers or factories that must be fresh every time. Experts also use custom context providers to manage complex lifecycles.
Connections
Dependency Injection
Provider scope is a feature built on top of dependency injection principles.
Understanding how dependency injection manages object lifecycles clarifies why scopes control instance creation and sharing.
HTTP Request Lifecycle
Request scope aligns provider lifetimes with the HTTP request lifecycle.
Knowing the HTTP request flow helps understand why request scope isolates data per user interaction.
Object Pooling in Game Development
Both manage object lifetimes to optimize resource use and performance.
Recognizing that scopes and object pools solve similar problems in different fields deepens understanding of resource management.
Common Pitfalls
#1Injecting a request-scoped provider into a singleton service.
Wrong approach:@Injectable() export class SingletonService { constructor(private readonly reqService: RequestScopedService) {} }
Correct approach:@Injectable({ scope: Scope.REQUEST }) export class RequestScopedService {} @Injectable({ scope: Scope.REQUEST }) export class SingletonService { constructor(private readonly reqService: RequestScopedService) {} }
Root cause:Singleton services are created once and cannot receive new instances per request, so request-scoped providers lose their scope when injected into singletons.
#2Marking all providers as transient without need.
Wrong approach:@Injectable({ scope: Scope.TRANSIENT }) export class HeavyService {}
Correct approach:@Injectable() export class HeavyService {}
Root cause:Misunderstanding transient scope leads to excessive instance creation, increasing memory use and hurting performance.
#3Assuming request scope shares data across multiple requests from the same user.
Wrong approach:Storing user session data in a request-scoped provider expecting it to persist across requests.
Correct approach:Use external session storage or cookies to persist user data across requests; keep request-scoped providers stateless or per-request only.
Root cause:Confusing request scope with session scope causes incorrect assumptions about data persistence.
Key Takeaways
Provider scope in NestJS controls how often and when service instances are created and shared.
Default scope creates a singleton shared across the app, request scope creates a new instance per HTTP request, and transient scope creates a new instance every injection.
Choosing the right scope prevents bugs related to shared state and improves app performance and safety.
Scopes affect the dependency injection tree and must be used carefully to avoid unexpected instance sharing or memory issues.
Understanding provider scopes is essential for building scalable, maintainable, and secure NestJS applications.