The Dependency Injection (DI) system manages component instantiation, dependency resolution, and instance lifecycle in NestJS. The core consists of NestContainer (holds all modules), Injector (resolves and instantiates dependencies), Module (organizes providers), and InstanceWrapper (wraps provider instances with metadata).
The DI system has four primary components that work together during application bootstrap and runtime:
DI System Component Interaction
Sources:
NestContainer maintains the module graph and coordinates global operations. Each Module instance stores providers, controllers, and injectables in separate maps, tracks imports/exports, and has a calculated distance from the root module.
Container Structure
The container hierarchy operates through these mechanisms:
Module Registration: NestContainer.addModule() calls ModuleCompiler.compile() to generate a unique token, then creates a Module instance and stores it in modules map at packages/core/injector/container.ts92-127
Provider Registration: Module.addProvider() creates an InstanceWrapper and adds it to _providers map. Custom providers use addCustomClass(), addCustomValue(), addCustomFactory(), or addCustomUseExisting() at packages/core/injector/module.ts244-285
Global Scope Binding: NestContainer.bindGlobalScope() iterates modules and calls bindGlobalsToImports() to link global modules to all other modules at packages/core/injector/container.ts318-333
Module Distance: DependenciesScanner.calculateModulesDistance() uses TopologyTree to calculate each module's depth from root, stored in Module.distance property at packages/core/scanner.ts397-416
Sources:
NestJS supports four provider types defined in packages/common/interfaces/modules/provider.interface.ts10-167 Each type has distinct registration and instantiation behavior:
| Provider Type | Interface | Module Method | InstanceWrapper Configuration |
|---|---|---|---|
| Class Provider | ClassProvider<T> with useClass | addCustomClass() | metatype: useClass, inject: null, constructor called |
| Value Provider | ValueProvider<T> with useValue | addCustomValue() | metatype: null, instance: useValue, isResolved: true |
| Factory Provider | FactoryProvider<T> with useFactory | addCustomFactory() | metatype: useFactory, inject: inject[], factory invoked |
| Existing Provider | ExistingProvider<T> with useExisting | addCustomUseExisting() | inject: [useExisting], isAlias: true, references existing |
Provider Registration Flow
The registration process flows through DependenciesScanner:
reflectProviders() reads MODULE_METADATA.PROVIDERS from decorator metadata at packages/core/scanner.ts230-242insertProvider() checks isCustomProvider() (has provide property) at packages/core/scanner.ts438-485NestContainer.addProvider() → Module.addProvider() → creates InstanceWrapper with metatype set to classModule.addCustomProvider() routes to specific handler based on type detection at packages/core/injector/module.ts305-324insertController() → NestContainer.addController() → Module.addController(), stored in _controllers map at packages/core/scanner.ts532-534insertInjectable() → NestContainer.addInjectable() → Module.addInjectable() with enhancerSubtype at packages/core/scanner.ts487-520Sources:
Injector resolves dependencies through a multi-phase process involving metadata reflection, token resolution, recursive lookup across modules, and instance creation.
Complete Dependency Resolution Flow
Dependency metadata is extracted using reflect-metadata:
Constructor Parameters: reflectConstructorParams() reads PARAMTYPES_METADATA (set by TypeScript's emitDecoratorMetadata) at packages/core/injector/injector.ts440-448
Self-Declared Dependencies: reflectSelfParams() reads SELF_DECLARED_DEPS_METADATA (set by @Inject() decorator) at packages/core/injector/injector.ts454-456
Optional Dependencies: reflectOptionalParams() reads OPTIONAL_DEPS_METADATA (set by @Optional() decorator) at packages/core/injector/injector.ts450-452
Property Dependencies: reflectProperties() reads PROPERTY_DEPS_METADATA and OPTIONAL_PROPERTY_DEPS_METADATA at packages/core/injector/injector.ts795-805
Factory Dependencies: getFactoryProviderDependencies() processes the inject array, identifying OptionalFactoryDependency objects at packages/core/injector/injector.ts400-438
When a dependency is not found in the current module, lookupComponentInImports() performs a breadth-first search:
moduleRegistry: Set<string> to track visited modules and prevent infinite loops at packages/core/injector/injector.ts638-703moduleRef.imports by checking if traversing (only considers exported modules) at packages/core/injector/injector.ts653-658exports.has(name) && providers.has(name) at packages/core/injector/injector.ts667isTraversing = true parameter at packages/core/injector/injector.ts668-677loadProvider() for unresolved providers found in imports at packages/core/injector/injector.ts537-542instantiateClass() creates instances based on provider type at packages/core/injector/injector.ts819-863:
isNil(inject), then executes new metatype(...instances) or merges with existing prototype if wrapper.forwardRef is truemetatype(...instances) and awaits if it returns a PromiseThe instance is passed through instanceDecorator() if configured (for instrumentation) and stored via setInstanceByContextId().
Sources:
NestJS supports three injection scopes defined in packages/common/interfaces/scope-options.interface.ts4-19: DEFAULT, TRANSIENT, and REQUEST. Each scope uses different storage strategies in InstanceWrapper.
Scope-based Instance Storage Architecture
InstanceWrapper.getInstanceByContextId() implements scope-specific retrieval logic at packages/core/injector/instance-wrapper.ts125-142:
DEFAULT scope:
- Always returns instance from STATIC_CONTEXT
- Single instance shared across entire application
REQUEST scope:
- If contextId === STATIC_CONTEXT, returns from values.get(STATIC_CONTEXT)
- Otherwise, returns from values.get(contextId) or clones static instance
- Each HTTP request gets its own ContextId via createContextId()
TRANSIENT scope:
- If inquirerId provided, calls getInstanceByInquirerId(contextId, inquirerId)
- Uses transientMap.get(inquirerId).get(contextId)
- Each consumer (parent provider) gets unique instances
- Inquirer is the InstanceWrapper requesting the dependency
ContextId is created per request and propagated through the resolution chain:
createContextId() generates {id: number} for each HTTP request at packages/core/helpers/context-id-factory.tsNestContainer.registerRequestProvider() stores the request object with its ContextId at packages/core/injector/container.ts356-362loadInstance() calls pass contextId parameter through the resolution treedurable: true flag with REQUEST scope, making subtree persistent across requests via isDependencyTreeDurable() at packages/core/injector/instance-wrapper.ts234-264For TRANSIENT scope, the inquirer parameter identifies the parent requesting the dependency:
loadInstance() call passes inquirer?: InstanceWrapper parameter at packages/core/injector/injector.ts128-205getInquirerId(inquirer) returns inquirer?.id at packages/core/injector/injector.ts964-968getEffectiveInquirer() handles TRANSIENT → TRANSIENT chains, returning parentInquirer for nested transients at packages/core/injector/injector.ts977-989getInstanceByInquirerId() retrieves from transientMap.get(inquirerId).get(contextId) at packages/core/injector/instance-wrapper.ts144-157This ensures each consumer of a transient provider gets its own instance, even within the same request context.
Sources:
The DI system detects and resolves circular dependencies using SettlementSignal and forward references.
Circular Dependency Detection Flow
SettlementSignal tracks the resolution chain to detect cycles at packages/core/injector/settlement-signal.ts1-50:
Resolution process:
applySettlementSignal() creates SettlementSignal and stores in wrapper.settlementSignal at packages/core/injector/injector.ts278-288settlementSignal.insertRef(wrapper.id) adds current wrapper to resolution chain at packages/core/injector/injector.ts535settlementSignal.isCycle(inquirer.id) checks if inquirer is already resolving at packages/core/injector/injector.ts143CircularDependencyExceptionforwardRef() allows referencing classes before definition:
resolveParamToken() checks if param.forwardRef exists at packages/core/injector/injector.ts489-498wrapper.forwardRef = true flagparam.forwardRef() to get actual class tokenresolveComponentHost(), checks !instanceHost.isResolved && instanceWrapper.forwardRef at packages/core/injector/injector.ts543-563donePromise.then()Object.assign(instanceHost.instance, new metatype(...)) to merge with pre-created prototype at packages/core/injector/injector.ts844-848This enables:
Three mechanisms for optional dependencies:
Constructor Optionals: @Optional() decorator adds to OPTIONAL_DEPS_METADATA, checked in resolveConstructorParams() catch block at packages/core/injector/injector.ts379-384
Property Optionals: @Optional() on properties adds to OPTIONAL_PROPERTY_DEPS_METADATA, checked via item.isOptional in resolveProperties() at packages/core/injector/injector.ts782-786
Factory Optionals: OptionalFactoryDependency object with {token, optional: true} in inject array, processed by getFactoryProviderDependencies() at packages/core/injector/injector.ts400-438
When resolution fails and dependency is optional, returns undefined instead of throwing.
Sources:
InstanceWrapper stores provider metadata and manages per-context instances using WeakMaps and internal metadata storage.
InstanceWrapper Architecture
InstanceWrapper maintains dependency metadata in INSTANCE_METADATA_SYMBOL internal property at packages/core/injector/instance-wrapper.ts55-59:
Metadata is populated by:
addCtorMetadata(index, wrapper): Stores constructor param at index at packages/core/injector/instance-wrapper.ts198-203addPropertiesMetadata(key, wrapper): Stores property dependency at packages/core/injector/instance-wrapper.ts209-217addEnhancerMetadata(wrapper): Stores enhancer (guard/pipe/etc.) at packages/core/injector/instance-wrapper.ts223-228Retrieved by:
getCtorMetadata(): Returns dependencies array at packages/core/injector/instance-wrapper.ts205-207getPropertiesMetadata(): Returns properties array at packages/core/injector/instance-wrapper.ts219-221getEnhancersMetadata(): Returns enhancers array at packages/core/injector/instance-wrapper.ts230-232Used by Injector to traverse dependency tree in introspectDepsAttribute() at packages/core/injector/instance-wrapper.ts266-298
InstanceWrapper analyzes its dependency tree to determine scope properties:
Static Tree Detection (isDependencyTreeStatic() at packages/core/injector/instance-wrapper.ts300-320):
false if wrapper has scope === Scope.REQUESTisTreeStatic propertyDurable Tree Detection (isDependencyTreeDurable() at packages/core/injector/instance-wrapper.ts234-264):
durable ?? falseisTreeDurable propertyDependenciesScanner.calculateModulesDistance() uses TopologyTree to compute module distances from root at packages/core/scanner.ts397-416:
TopologyTree builds an acyclic tree:
TreeNode for each module with parent/children relationships at packages/core/injector/topology-tree/topology-tree.ts8-15traverseAndMapToTree() processes imports recursively, handling cycles at packages/core/injector/topology-tree/topology-tree.ts25-56relink() to move closer to root at packages/core/injector/topology-tree/tree-node.ts19-24walk() performs depth-first traversal to set distances at packages/core/injector/topology-tree/topology-tree.ts17-23Distance is used for lifecycle hook execution order (modules sorted by distance in getModulesToTriggerHooksOn() at packages/core/nest-application-context.ts490-504).
Sources:
The DI system initializes during application bootstrap through a coordinated sequence of scanning, registration, and instantiation.
Application Bootstrap Sequence
InstanceLoader.createInstancesOfDependencies() orchestrates the two-phase loading at packages/core/injector/instance-loader.ts25-60:
Phase 1: Prototype Creation
createPrototypes(modules) at packages/core/injector/instance-loader.ts40-46createPrototypesOfProviders(), createPrototypesOfInjectables(), createPrototypesOfControllers()Injector.loadPrototype() calls wrapper.createPrototype() which executes Object.create(metatype.prototype) at packages/core/injector/instance-wrapper.ts358-364Phase 2: Instance Creation
createInstances(modules) with parallel resolution using Promise.all() at packages/core/injector/instance-loader.ts48-60createInstancesOfProviders(), createInstancesOfInjectables(), createInstancesOfControllers()Injector.loadProvider/loadInjectable/loadController() methodsInjector.loadInstance() for actual instantiationGraphInspector.inspectInstanceWrapper() records resolution for debuggingAfter instance loading, global enhancers (guards, pipes, interceptors, filters) are registered:
DependenciesScanner.addScopedEnhancersMetadata() at packages/core/scanner.ts629-649:
applicationProvidersApplyMap (APP_GUARD, APP_PIPE, etc.)addEnhancerMetadata()DependenciesScanner.applyApplicationProviders() at packages/core/scanner.ts651-688:
ApplicationConfig.addGlobalInterceptor/Guard/Pipe/Filter() for DEFAULT scopeApplicationConfig.addGlobalRequestInterceptor/Guard/Pipe/Filter() for REQUEST/TRANSIENT scopeSources:
The DI system integrates deeply with NestJS application lifecycle through several key processes:
DependenciesScanner uses reflectImports(), reflectProviders(), reflectControllers() to build the module dependency graphNestApplicationContext.registerRequestByContextId() integrates with the REQUEST scope for per-request provider instancesinsertInjectable() with specific enhancerSubtype valuesNestApplicationContext.get() and resolve() methods delegate to the DI system through find() and resolvePerContext()The InstanceLoader coordinates the creation of all instances during application startup, calling createInstancesOfProviders(), createInstancesOfControllers(), and createInstancesOfInjectables() in sequence.
Sources:
The NestJS Dependency Injection system provides a powerful foundation for the framework, enabling modular, testable, and maintainable applications. Its sophisticated dependency resolution, scoping mechanisms, and circular dependency handling make it suitable for complex enterprise applications while remaining performant and intuitive to use.
The DI system is built on solid OOP principles and inspired by Angular's DI system, providing a familiar experience for developers coming from that ecosystem while adding unique features tailored for server-side NodeJS applications.
Refresh this wiki