Home
NestJS
NestJS - Provider Scopes
September 06, 2023
1 min

Table Of Contents

01
🎯 Understanding Provider Scopes in NestJS (Complete Guide)
02
🧠 What is Provider Scope?
03
🔥 The 3 Types of Provider Scopes
04
1️⃣ Singleton Scope (DEFAULT) 🟢
05
2️⃣ Request Scope 🟡
06
3️⃣ Transient Scope 🔵
07
🧠 Scope Comparison
08
⚠️ Scope Propagation (VERY IMPORTANT)
09
⚠️ Performance Impact
10
🚀 Real-world Patterns
11
🔥 Advanced Example — Request ID Tracking
12
🎯 When to Use Each Scope
13
🧠 Mental Model
14
🎯 Best Practices
15
🎤 Interview Answer
16
🚀 Final Thought

🎯 Understanding Provider Scopes in NestJS (Complete Guide)

When building scalable backend systems with NestJS, Provider Scope is a concept that directly impacts:

  • Performance ⚡
  • Memory usage 🧠
  • Application architecture 🏗️

If you misuse it, your app can become slow and hard to debug. If you use it correctly, you get clean, efficient, production-ready systems.


🧠 What is Provider Scope?

Provider Scope defines the lifecycle of a provider — how often it is created and how long it lives


🔥 The 3 Types of Provider Scopes


1️⃣ Singleton Scope (DEFAULT) 🟢

@Injectable()
export class UsersService {}

👉 Equivalent to:

@Injectable({ scope: Scope.DEFAULT })

✅ Behavior

  • Created once when the app starts
  • Shared across the entire application
App Start
Create UsersService (1 instance)
Reuse everywhere

✅ Real-world usage

  • Business logic services
  • Database access (TypeORM repositories)
  • API integrations

🚀 Example

@Injectable()
export class UsersService {
findAll() {
return 'all users';
}
}

👉 Every controller uses the same instance


2️⃣ Request Scope 🟡

import { Scope } from '@nestjs/common';
@Injectable({ scope: Scope.REQUEST })
export class RequestService {}

✅ Behavior

  • New instance for every HTTP request
Request A → new instance
Request B → new instance

🔥 Real-world use cases

  • Current user (req.user)
  • Request tracking / logging
  • Multi-tenant systems

🚀 Example

import { Injectable, Scope, Inject } from '@nestjs/common';
import { REQUEST } from '@nestjs/core';
import { Request } from 'express';
@Injectable({ scope: Scope.REQUEST })
export class RequestContextService {
constructor(@Inject(REQUEST) private req: Request) {}
getUser() {
return this.req.user;
}
}

🧩 Usage in a service

constructor(private ctx: RequestContextService) {}

👉 Each request gets its own user context


3️⃣ Transient Scope 🔵

@Injectable({ scope: Scope.TRANSIENT })
export class LoggerService {}

✅ Behavior

  • New instance every time it is injected
Injected 3 times → 3 instances

🔥 Real-world use cases

  • Logging utilities
  • Stateless helpers
  • Lightweight services

🚀 Example

@Injectable({ scope: Scope.TRANSIENT })
export class LoggerService {
log(message: string) {
console.log(message);
}
}

🧠 Scope Comparison

Singleton → 1 instance for whole app
Request → 1 instance per request
Transient → 1 instance per injection

⚠️ Scope Propagation (VERY IMPORTANT)

This is where most developers make mistakes.


❗ Example

@Injectable()
export class UsersService {
constructor(private ctx: RequestContextService) {}
}

👉 If RequestContextService is request-scoped:

UsersService becomes REQUEST-SCOPED too

🔥 Chain Effect

RequestContextService (REQUEST)
UsersService (REQUEST)
Controller (REQUEST)

👉 This can slow down your app significantly


⚠️ Performance Impact

ScopePerformance
SingletonFastest 🚀
RequestSlower ⚠️
TransientDepends ⚠️

❗ Why Request Scope is expensive

  • Creates new instances per request
  • More memory usage
  • More garbage collection

🚀 Real-world Patterns


✅ Pattern 1 — Default architecture

Controller (Singleton)
Service (Singleton)
Repository (Singleton)

👉 Fast and efficient


✅ Pattern 2 — Add request context

Controller
Service
RequestContextService (Request)

👉 Only one part is request-scoped


❌ Bad Pattern

Everything → Request Scope ❌

👉 Very slow system


🔥 Advanced Example — Request ID Tracking

@Injectable({ scope: Scope.REQUEST })
export class RequestIdService {
private id = Math.random().toString(36);
getId() {
return this.id;
}
}

Use it

constructor(private requestId: RequestIdService) {}
log(message: string) {
console.log(`[${this.requestId.getId()}] ${message}`);
}

👉 Each request has unique trace ID


🎯 When to Use Each Scope


🟢 Singleton (Default)

Use when:

  • General services
  • Business logic
  • Database operations

🟡 Request Scope

Use when:

  • Need request-specific data
  • Auth / current user
  • Multi-tenant apps

🔵 Transient

Use when:

  • Stateless utilities
  • Logging
  • Lightweight helpers

🧠 Mental Model

Singleton → Shared brain
Request → Per-user brain
Transient → Per-use brain

🎯 Best Practices

  • ✅ Use Singleton by default
  • ✅ Use Request scope only when necessary
  • ✅ Keep request-scoped providers small
  • ❌ Don’t inject request scope everywhere
  • ❌ Avoid making core services request-scoped

🎤 Interview Answer

“Provider scope in NestJS defines the lifecycle of a provider. Singleton scope creates one shared instance, request scope creates a new instance per request, and transient scope creates a new instance per injection. Request scope should be used carefully due to performance implications.”


🚀 Final Thought

Provider scope is not just a technical detail—it’s an architecture decision.

Used correctly:

Fast + Scalable + Maintainable system

Used incorrectly:

Slow + Memory-heavy + Hard-to-debug system

👉 Master this, and you’re already thinking like a senior backend engineer.


Tags

#NestJS

Share

Related Posts

NextJS
NestJS - Setup with PostgreSQL & Docker
September 06, 2023
1 min
© 2026, All Rights Reserved.
Powered By

Social Media

githublinkedinyoutube