This document explains the foundational architectural patterns of the NestJS framework, including the dependency injection system, module organization, and application lifecycle management. These core patterns form the basis upon which all NestJS applications are built.
For detailed information about specific aspects:
NestJS is built on three foundational pillars that work together to provide a complete application framework. The architecture separates concerns into distinct layers: application bootstrap (NestFactory), dependency management (NestContainer), module discovery (DependenciesScanner), instance creation (Injector), and runtime organization (Module, InstanceWrapper).
Core Architecture Components and Their Relationships
Sources: packages/core/nest-factory.ts43-325 packages/core/nest-application.ts54-95 packages/core/nest-application-context.ts40-81 packages/core/injector/container.ts31-60 packages/core/scanner.ts75-84 packages/core/injector/injector.ts86-107 packages/core/injector/instance-loader.ts1-100
The NestContainer class (located in packages/core/injector/container.ts) serves as the central registry for all modules and their providers. It maintains a ModulesContainer (implemented as Map<string, Module>) that stores all registered modules and coordinates the dependency resolution process.
NestContainer Key Properties:
| Property | Type | Purpose |
|---|---|---|
modules | ModulesContainer | Map of all registered modules by token |
globalModules | Set<Module> | Modules marked with @Global() decorator |
dynamicModulesMetadata | Map<string, Partial<DynamicModule>> | Metadata from dynamic module definitions |
internalProvidersStorage | InternalProvidersStorage | Framework internal providers (HttpAdapter, etc.) |
moduleCompiler | ModuleCompiler | Compiles module definitions into tokens |
Sources: packages/core/injector/container.ts31-73 packages/core/injector/module.ts44-160
Each Module instance encapsulates a cohesive set of providers, controllers, and dependencies. The Module class (located in packages/core/injector/module.ts) serves as organizational boundaries and controls the visibility of providers through exports.
Module Internal Collections:
| Module Property | Type | Purpose |
|---|---|---|
_providers | Map<InjectionToken, InstanceWrapper> | Services and custom providers registered in this module |
_controllers | Map<InjectionToken, InstanceWrapper> | HTTP route handlers (classes decorated with @Controller()) |
_injectables | Map<InjectionToken, InstanceWrapper> | Guards, pipes, interceptors, filters |
_imports | Set<Module> | Other modules this module depends on |
_exports | Set<InjectionToken> | Tokens to expose to importing modules |
_middlewares | Map<InjectionToken, InstanceWrapper> | HTTP middleware registered in this module |
Module Metadata:
| Property | Type | Purpose |
|---|---|---|
_id | string | Unique identifier (UUID) |
_token | string | Module token for lookups |
_metatype | Type<any> | Original module class |
_distance | number | Distance from root (for initialization order) |
_isGlobal | boolean | Whether decorated with @Global() |
Sources: packages/core/injector/module.ts44-160
The NestContainer serves as the central registry for all modules and their providers. It maintains a ModulesContainer (a Map structure) that stores all registered modules and coordinates the dependency resolution process.
Sources: packages/core/injector/container.ts31-73 packages/core/injector/module.ts44-160
Each Module instance encapsulates a cohesive set of providers, controllers, and dependencies. Modules serve as organizational boundaries and control the visibility of providers through exports.
| Module Property | Type | Purpose |
|---|---|---|
_providers | Map<InjectionToken, InstanceWrapper> | Services and custom providers |
_controllers | Map<InjectionToken, InstanceWrapper> | HTTP route handlers |
_injectables | Map<InjectionToken, InstanceWrapper> | Guards, pipes, interceptors, filters |
_imports | Set<Module> | Dependency modules |
_exports | Set<InjectionToken> | Tokens to expose to importing modules |
_middlewares | Map<InjectionToken, InstanceWrapper> | HTTP middleware |
Sources: packages/core/injector/module.ts44-160
The InstanceWrapper<T> class (located in packages/core/injector/instance-wrapper.ts) wraps each provider and maintains its lifecycle state, scope, and instance references across different contexts (static, request-scoped, and transient).
InstanceWrapper Key Storage Mechanisms:
InstanceWrapper Core Methods:
| Method | Purpose |
|---|---|
getInstanceByContextId(contextId, inquirerId?) | Retrieve instance for given context and optional inquirer |
setInstanceByContextId(contextId, value, inquirerId?) | Store instance for given context |
isDependencyTreeStatic() | Determine if entire dependency tree is singleton |
isDependencyTreeDurable() | Determine if tree should persist across requests |
addCtorMetadata(index, wrapper) | Track constructor parameter dependency |
addPropertiesMetadata(key, wrapper) | Track property injection dependency |
cloneStaticInstance(contextId) | Create request-scoped copy from singleton prototype |
Sources: packages/core/injector/instance-wrapper.ts61-98 packages/core/injector/instance-wrapper.ts125-181 packages/core/injector/instance-wrapper.ts198-232
The DependenciesScanner class (located in packages/core/scanner.ts) walks through module metadata using TypeScript's reflection API to discover and register all dependencies. It uses Reflect.getMetadata() to extract decorator metadata from module classes.
DependenciesScanner Module Discovery Flow:
DependenciesScanner Key Methods:
| Method | Purpose | Metadata Key Used |
|---|---|---|
scan(module, options) | Entry point for scanning | - |
scanForModules(params) | Recursively discover all modules | MODULE_METADATA.IMPORTS |
reflectImports(module, token, context) | Extract and register imported modules | MODULE_METADATA.IMPORTS |
reflectProviders(module, token) | Extract and register providers | MODULE_METADATA.PROVIDERS |
reflectControllers(module, token) | Extract and register controllers | MODULE_METADATA.CONTROLLERS |
reflectExports(module, token) | Extract and register exports | MODULE_METADATA.EXPORTS |
reflectInjectables(component, token, metadataKey) | Extract guards, interceptors, pipes, filters | GUARDS_METADATA, INTERCEPTORS_METADATA, etc. |
Sources: packages/core/scanner.ts75-104 packages/core/scanner.ts106-175 packages/core/scanner.ts202-280 packages/core/scanner.ts282-332
The Injector class (located in packages/core/injector/injector.ts) resolves dependencies and instantiates providers. It handles circular dependency detection, scope management, and lazy instantiation.
Injector Dependency Resolution Process:
Injector Key Methods:
| Method | Purpose | Key Logic |
|---|---|---|
loadInstance(wrapper, collection, moduleRef, contextId, inquirer) | Main entry point for instance creation | Coordinates entire resolution process |
resolveConstructorParams(wrapper, moduleRef, inject, callback, contextId, inquirer, parentInquirer) | Resolve all constructor parameters | Uses Reflect.getMetadata(PARAMTYPES_METADATA) |
resolveProperties(wrapper, moduleRef, inject, contextId, inquirer, parentInquirer) | Resolve all property injections | Uses Reflect.getMetadata(PROPERTY_DEPS_METADATA) |
resolveSingleParam(wrapper, param, dependencyContext, moduleRef, contextId, inquirer, keyOrIndex) | Resolve one dependency | Handles undefined/circular deps |
lookupComponent(providers, moduleRef, dependencyContext, wrapper, contextId, inquirer, keyOrIndex) | Find provider in module | Searches local providers first |
lookupComponentInImports(moduleRef, name, wrapper, moduleRegistry, contextId, inquirer, keyOrIndex, isTraversing) | Find provider in imports | Traverses module import graph |
resolveComponentHost(moduleRef, instanceWrapper, contextId, inquirer) | Get or create instance | Triggers recursive loading if needed |
instantiateClass(instances, wrapper, targetMetatype, contextId, inquirer) | Create instance with dependencies | Calls new metatype(...instances) or factory |
applyProperties(instance, properties) | Inject property dependencies | Assigns resolved deps to instance properties |
Circular Dependency Handling:
The injector uses SettlementSignal (located in packages/core/injector/settlement-signal.ts) to detect circular dependencies. Each instance being resolved tracks its dependencies via settlementSignal.insertRef(id). If a cycle is detected during resolution, a CircularDependencyException is thrown.
Sources: packages/core/injector/injector.ts86-107 packages/core/injector/injector.ts128-205 packages/core/injector/injector.ts290-388 packages/core/injector/injector.ts458-487 packages/core/injector/injector.ts575-636 packages/core/injector/injector.ts705-793 packages/core/injector/injector.ts819-863 packages/core/injector/settlement-signal.ts1-49
NestJS supports three injection scopes with different instantiation strategies:
| Scope | Behavior | Use Case | Storage |
|---|---|---|---|
Scope.DEFAULT | Singleton per application | Stateless services | values.get(STATIC_CONTEXT) |
Scope.REQUEST | New instance per request | Request-specific state | values.get(contextId) |
Scope.TRANSIENT | New instance per injection point | Complete isolation | transientMap.get(inquirerId).get(contextId) |
Sources: packages/core/injector/instance-wrapper.ts121-142 packages/core/injector/instance-wrapper.ts366-422 packages/common/interfaces/scope-options.interface.ts1-39
NestJS provides a comprehensive lifecycle management system through NestApplicationContext and its subclasses. The lifecycle includes initialization hooks, graceful shutdown, and context management for dependency injection.
Lifecycle Hook Execution Order:
NestApplicationContext Lifecycle Methods:
| Method | Located In | Purpose |
|---|---|---|
init() | packages/core/nest-application-context.ts253-271 | Triggers onModuleInit and onApplicationBootstrap hooks |
callInitHook() | packages/core/nest-application-context.ts422-427 | Invokes onModuleInit on all modules |
callBootstrapHook() | packages/core/nest-application-context.ts447-452 | Invokes onApplicationBootstrap on all modules |
close(signal?) | packages/core/nest-application-context.ts277-284 | Graceful shutdown sequence |
callDestroyHook() | packages/core/nest-application-context.ts433-441 | Invokes onModuleDestroy |
callBeforeShutdownHook(signal) | packages/core/nest-application-context.ts472-480 | Invokes beforeApplicationShutdown |
callShutdownHook(signal) | packages/core/nest-application-context.ts458-466 | Invokes onApplicationShutdown |
enableShutdownHooks(signals?) | packages/core/nest-application-context.ts324-346 | Listen for OS signals (SIGTERM, SIGINT) |
Module Distance and Hook Execution:
Lifecycle hooks execute in order based on module distance from root (calculated by TopologyTree). Modules are sorted by distance, with global modules having distance = Number.MAX_VALUE to ensure their hooks run first during initialization and last during shutdown.
Sources: packages/core/nest-application-context.ts40-512 packages/core/nest-application.ts176-197 packages/core/scanner.ts397-416 packages/core/injector/topology-tree/topology-tree.ts1-53
The complete initialization sequence orchestrated by NestFactory.create() involves multiple phases: container setup, module scanning, instance creation, and lifecycle hook execution.
Complete Bootstrap Sequence:
Bootstrap Phase Details:
| Phase | Method | Key Operations | Files Involved |
|---|---|---|---|
| 1. Container Init | NestFactory.create() | Create NestContainer, DependenciesScanner, Injector, InstanceLoader | packages/core/nest-factory.ts201-248 |
| 2. Module Discovery | DependenciesScanner.scan() | Recursively discover all modules via scanForModules() | packages/core/scanner.ts86-104 packages/core/scanner.ts106-175 |
| 3. Dependency Registration | scanModulesForDependencies() | Extract metadata for imports, providers, controllers, exports | packages/core/scanner.ts202-280 |
| 4. Instance Creation | InstanceLoader.createInstancesOfDependencies() | Instantiate all providers and controllers | packages/core/injector/instance-loader.ts25-100 |
| 5. Application Assembly | new NestApplication() | Create main application instance | packages/core/nest-application.ts73-95 |
| 6. Initialization | NestApplication.init() | Register routes, call lifecycle hooks | packages/core/nest-application.ts176-197 |
Sources: packages/core/nest-factory.ts59-113 packages/core/nest-factory.ts201-248 packages/core/nest-application.ts73-95 packages/core/nest-application.ts176-197 packages/core/scanner.ts86-104 packages/core/injector/instance-loader.ts25-100
The framework uses reflection to extract module metadata:
Sources: packages/core/scanner.ts213-280 packages/core/scanner.ts602-607
NestJS supports multiple provider patterns:
| Provider Type | Interface | Registration Method |
|---|---|---|
| Class Provider | ClassProvider | addCustomClass() |
| Value Provider | ValueProvider | addCustomValue() |
| Factory Provider | FactoryProvider | addCustomFactory() |
| Existing Provider | ExistingProvider | addCustomUseExisting() |
| Standard Class | Type<Injectable> | Direct instantiation |
Example Provider Configurations:
Sources: packages/core/injector/module.ts287-455 packages/common/interfaces/modules/provider.interface.ts1-159
The injector resolves dependencies using a multi-phase approach:
PARAMTYPES_METADATAPROPERTY_DEPS_METADATASources: packages/core/injector/injector.ts290-388 packages/core/injector/injector.ts571-632 packages/core/injector/injector.ts815-859
Modules decorated with @Global() are automatically imported into all modules:
Sources: packages/core/injector/container.ts208-220 packages/core/injector/container.ts318-333
NestJS calculates module distance from the root to determine initialization order:
The TopologyTree traverses the module import graph to assign distances, ensuring lifecycle hooks execute in correct order (global modules first, then by depth).
Sources: packages/core/scanner.ts397-416 packages/core/injector/topology-tree/topology-tree.ts1-53 packages/core/injector/container.ts163-184
The NestJS core architecture consists of three primary systems working in concert:
These components leverage TypeScript decorators and reflection to provide a declarative, modular architecture while maintaining type safety and platform independence through the adapter pattern.
Refresh this wiki