Scope.REQUEST: Propagation Overhead & Transient Provider Instances
- Last updated
- Prerequisites:
- NestJS Dependency Injection Container
- Singleton vs Request vs Transient Scopes
- nestjs
- request-scope
- performance
- memory
- ioc-container
- scope
Read at your depth
The practical view
NestJS providers have three injection scopes: `DEFAULT` (singleton created once at startup), `REQUEST` (a new instance is created for every incoming request and garbage collected after), and `TRANSIENT` (a new instance per injection site). You set it via `@Injectable({ scope: Scope.REQUEST })`. Because `REQUEST` scope re-instantiates services on every HTTP call, it significantly degrades throughput (can drop RPS by 30-50%).
The same idea in other frameworks
`@Injectable({ scope: Scope.REQUEST })`.Spring `@Scope(value = WebApplicationContext.SCOPE_REQUEST)`.FastAPI `Depends()` function dependencies (instantiated per request by default).`AsyncLocalStorage` storing request context without re-instantiating services.Legacy vs modern
Scope.REQUEST Bubble vs AsyncLocalStorage Singleton Pattern
`Scope.REQUEST` forces the entire controller tree to re-instantiate; `AsyncLocalStorage` preserves singletons with zero allocation overhead.
@Injectable({ scope: Scope.REQUEST }) export class TenantService { // ❌ Re-allocates tree per requestconst tenantStorage = new AsyncLocalStorage<string>(); // ✅ NEW: 100% Singleton performanceInterview gotchas
Context
Interviewers test your knowledge of how dependency injection scopes propagate.
The mechanical answer
Scope Bubbling means that **a provider's scope bubbles up the dependency chain**. If Service C is `Scope.REQUEST`, and Service B injects Service C, Service B *must* also become `Scope.REQUEST`. If Controller A injects Service B, Controller A also becomes `Scope.REQUEST`. A single request-scoped service at the bottom of the graph forces all dependent ancestors to be re-created on every request.
Trap
Do not assume that making only a database service request-scoped will leave your controllers as singletons; the entire tree above it becomes request-scoped.
Context
Checks performance-oriented architecture design for enterprise applications.
The mechanical answer
Use **Node.js `AsyncLocalStorage`** in an authentication middleware to store the tenant context for the asynchronous execution chain. Keep all NestJS services and controllers as `DEFAULT` (singletons). When database queries run, the singleton repository reads the active tenant ID from `AsyncLocalStorage.getStore()` without any DI re-instantiation overhead.
Trap
Migrating from `Scope.REQUEST` to `AsyncLocalStorage` often results in a 3x to 5x throughput improvement in benchmarks.