Dependency injection
Tokens and metadata, request scope and DI sub-trees, durable contexts, and multi-tenant data isolation in NestJS.
Implicit and explicit injection
NestJS resolves constructor dependencies in two ways:
- Implicit: parameter types are read from TypeScript metadata (
reflect-metadata). Works for classes whenemitDecoratorMetadatais enabled and the provider is registered by class token. - Explicit:
@Inject(token)names the exact token—required for interfaces (they erase at compile time), string or symbol tokens,useValue/useFactoryproviders, and many dynamic registrations.
Prefer implicit injection for class dependencies to reduce boilerplate and improve refactor safety. Use symbols (or well-namespaced string constants) for custom tokens to avoid collisions and typos.
export const COFFEES_DATA_SOURCE = Symbol("COFFEES_DATA_SOURCE");
@Injectable()
export class CoffeesService {
constructor(@Inject(COFFEES_DATA_SOURCE) private readonly dataSource: CoffeesDataSource) {}
}@Module({
providers: [
CoffeesService,
{ provide: COFFEES_DATA_SOURCE, useValue: /* concrete adapter */ [] },
],
})
export class CoffeesModule {}Alternatively, use a concrete class as the token when the abstraction does not need to remain an interface.
Application graph and DI sub-trees
The application graph is the static module and provider graph built at bootstrap. A DI sub-tree is an isolated resolution context keyed by a context id—for example, one tree per HTTP request for request-scoped providers.
- Request-scoped providers are created when a triggering scope exists (such as an HTTP request).
- Scope bubbles: if
Adepends on request-scopedB, thenAbecomes request-scoped as well. ModuleRef.resolve()can create or reuse sub-trees; associating the same context id with the same request yields the same instances for that tree.
import { Injectable, Scope } from "@nestjs/common";
import { REQUEST } from "@nestjs/core";
import type { Request } from "express";
@Injectable({ scope: Scope.REQUEST })
export class TagsService {
constructor(@Inject(REQUEST) private readonly request: Request) {}
}Context id lifecycle: context ids are ordinary objects; retaining them in long-lived collections prevents garbage collection of the associated sub-tree and can cause memory leaks. Prefer letting scopes end with the request unless you deliberately extend lifetime.
Propagating request context to asynchronous handlers
Event subscribers are not request-scoped. When a handler must read request-scoped services, create a context id early, register the request with registerRequestByContextId, pass the context id on the event payload, and resolve request-scoped providers inside the handler with moduleRef.resolve(Service, contextId). Avoid storing large payloads on events; keep identifiers and context handles minimal.
Durable providers and shared sub-trees
Request-scoped graphs recreated on every request can be expensive at high throughput. Durable behavior (via stable context id factories) groups requests that share stable characteristics—tenant id, locale, API version—and reuses DI sub-trees for that group when varying per-request data does not affect provider wiring.
Typical flow:
- Derive a stable context key from headers, JWT claims, or subdomain.
- Create or reuse a context id for that key.
- Resolve services through that context; register the current request when the framework requires it.
Plan eviction for long-running processes so context maps do not grow without bound.
Multi-tenancy strategies
Multi-tenancy serves many customers from one deployment while isolating data and configuration.
Silo (database per tenant)
Strong isolation and straightforward per-tenant operations; higher cost and more complex onboarding and cross-tenant reporting.
Pool (shared database and schema)
Efficient and simple to operate; requires strict query discipline and auditing to prevent cross-tenant leakage.
Bridge (shared database, schema per tenant)
Balances isolation and efficiency; connection and migration tooling must understand schema routing.
Tenant identification in Nest
Common sources: subdomain, X-Tenant-ID header, JWT claim, API key lookup, or path prefix. Centralize resolution (for example in middleware) and reuse the result in logging, persistence, and durable context factories.
Related
- Architecture — ports and adapters for tenant-aware implementations.
- Extensibility and tooling —
ModuleRefand discovery patterns. - Debugging — missing providers and interface tokens.
