Lesson 36-NestJS Source Code Architecture

NestJS Core Module Source Code

Module System Source Code Implementation

Module Class Core Implementation:

// packages/core/src/module.ts
export class Module {
  constructor(
    public readonly metatype: Type<any>,
    public readonly scope: Scope[],
    public readonly providers: Provider[],
    public readonly controllers: Type<any>[],
    public readonly imports: ModuleWithProviders[],
    public readonly exports: Provider[]
  ) {}

  // Resolve module dependency graph
  static async create(
    metatype: Type<any>,
    container: NestContainer,
    scope: Scope[]
  ): Promise<Module> {
    const { providers, controllers, imports, exports } = await this.reflectMetadata(metatype)
    const moduleRef = new Module(metatype, scope, providers, controllers, imports, exports)

    // Register module to container
    container.addModule(moduleRef)

    // Recursively load imported modules
    await this.loadImports(moduleRef, container)

    return moduleRef
  }

  // Reflect module metadata
  private static async reflectMetadata(metatype: Type<any>): Promise<{
    providers: Provider[]
    controllers: Type<any>[]
    imports: ModuleWithProviders[]
    exports: Provider[]
  }> {
    // Use reflection API to get decorator metadata
    const providers = Reflect.getMetadata(PROVIDERS, metatype) || []
    const controllers = Reflect.getMetadata(CONTROLLERS, metatype) || []
    const imports = Reflect.getMetadata(IMPORTS, metatype) || []
    const exports = Reflect.getMetadata(EXPORTS, metatype) || []

    return { providers, controllers, imports, exports }
  }
}

Dependency Graph Construction Process:

// packages/core/src/module/container.ts
export class NestContainer {
  private modules = new Map<string, Module>()
  private moduleGraph = new Map<string, Set<string>>()

  // Add module and build dependency graph
  addModule(module: Module, scope?: Scope[]) {
    const moduleName = this.getModuleName(module.metatype)

    // Register module
    this.modules.set(moduleName, module)

    // Build dependency graph
    this.moduleGraph.set(moduleName, new Set())
    module.imports.forEach(imported => {
      const importedName = this.getModuleName(imported.module.metatype)
      this.moduleGraph.get(moduleName)?.add(importedName)
    })
  }

  // Load modules in topological order
  async loadModules() {
    const sortedModules = this.topologicalSort()
    for (const moduleName of sortedModules) {
      const module = this.modules.get(moduleName)
      await this.initializeModule(module)
    }
  }

  // Topological sort algorithm implementation
  private topologicalSort(): string[] {
    // Kahn's algorithm for dependency graph sorting
    const inDegree = new Map<string, number>()
    const queue: string[] = []
    const result: string[] = []

    // Initialize in-degree
    this.moduleGraph.forEach((deps, moduleName) => {
      inDegree.set(moduleName, 0)
    })

    // Calculate in-degree
    this.moduleGraph.forEach((deps, moduleName) => {
      deps.forEach(dep => {
        inDegree.set(dep, (inDegree.get(dep) || 0) + 1)
      })
    })

    // Enqueue nodes with in-degree 0
    this.moduleGraph.forEach((_, moduleName) => {
      if (inDegree.get(moduleName) === 0) {
        queue.push(moduleName)
      }
    })

    // Perform topological sort
    while (queue.length) {
      const moduleName = queue.shift()!
      result.push(moduleName)

      this.moduleGraph.get(moduleName)?.forEach(dep => {
        inDegree.set(dep, inDegree.get(dep)! - 1)
        if (inDegree.get(dep) === 0) {
          queue.push(dep)
        }
      })
    }

    return result
  }
}

Dependency Injection Container Source Code Implementation

Injector Core Implementation:

// packages/core/src/di/injector.ts
export class Injector {
  async resolve<T>(provider: Provider, contextId = STATIC_CONTEXT): Promise<T> {
    // 1. Handle different types of Provider
    if (isValueProvider(provider)) {
      return provider.useValue
    } else if (isFactoryProvider(provider)) {
      return this.resolveFactoryProvider(provider, contextId)
    } else if (isClassProvider(provider)) {
      return this.resolveClassProvider(provider, contextId)
    }
  }

  // Resolve class Provider
  private async resolveClassProvider(
    provider: ClassProvider,
    contextId: ContextId
  ): Promise<any> {
    // 1. Get or create instance
    const instanceWrapper = this.getInstanceWrapper(provider.provide)

    if (!instanceWrapper.isResolved) {
      // 2. Resolve constructor dependencies
      const dependencies = this.reflectConstructorParams(provider.provide)
      const resolvedDependencies = await this.resolveDependencies(dependencies, contextId)

      // 3. Instantiate class
      instanceWrapper.instance = new provider.provide(...resolvedDependencies)
      instanceWrapper.isResolved = true
    }

    return instanceWrapper.instance
  }

  // Reflect constructor parameters
  private reflectConstructorParams(provider: Type<any>): any[] {
    const paramTypes = Reflect.getMetadata(PARAMTYPES_METADATA, provider) || []
    const paramInjections = Reflect.getMetadata(INJECT_METADATA, provider) || []

    return paramTypes.map((type, index) => {
      return paramInjections[index] || type
    })
  }
}

Provider Resolution Process:

// packages/core/src/di/provider.ts
export function isValueProvider(provider: any): provider is ValueProvider {
  return provider && !!provider.useValue
}

export function isFactoryProvider(provider: any): provider is FactoryProvider {
  return provider && !!provider.useFactory
}

export function isClassProvider(provider: any): provider is ClassProvider {
  return provider && !!provider.useClass
}

// Provider type definition
export type Provider =
  | ValueProvider
  | FactoryProvider
  | ClassProvider
  | ExistingProvider
  | any[]

Controller Source Code Analysis

Route Registration Process:

// packages/core/src/router/router-explorer.ts
export class RouterExplorer {
  explore(controller: Type<any>, module: Module) {
    // 1. Get controller metadata
    const routes = this.reflectRoutes(controller)

    // 2. Create handler for each route method
    routes.forEach(route => {
      const handler = this.createHandler(controller, route)

      // 3. Register route to Express/Koa
      this.registerRoute(module, controller, route, handler)
    })
  }

  // Reflect route metadata
  private reflectRoutes(controller: Type<any>): RouteMetadata[] {
    const prototype = Object.getPrototypeOf(controller.prototype)
    const methods = Object.getOwnPropertyNames(prototype)

    return methods
      .filter(method => this.isRouteMethod(prototype[method]))
      .map(method => ({
        method: prototype[method],
        path: Reflect.getMetadata(PATH_METADATA, controller.prototype, method),
        methodMetadata: Reflect.getMetadata(METHOD_METADATA, controller.prototype, method)
      }))
  }

  // Create request handler
  private createHandler(controller: Type<any>, route: RouteMetadata) {
    return async (req: Request, res: Response, next: NextFunction) => {
      // 1. Resolve dependency injection
      const instance = this.getInstance(controller)

      // 2. Call controller method
      const result = await instance[route.method.name](req, res, next)

      // 3. Handle response
      if (result instanceof Observable) {
        result.subscribe(data => res.send(data))
      } else {
        res.send(result)
      }
    }
  }
}

Request Processing Flow:

// packages/core/src/router/router-execution-context.ts
export class RouterExecutionContext {
  create(
    instance: Controller,
    callback: Function,
    metadataKey: string
  ): (...args: any[]) => any {
    return async (req: Request, res: Response, next: NextFunction) => {
      // 1. Resolve method parameters
      const args = this.reflectArgs(instance, callback, req, res, next)

      // 2. Execute method
      const result = await callback.apply(instance, args)

      // 3. Handle return value
      if (result instanceof Observable) {
        return result.pipe(
          map(data => res.send(data)),
          catchError(err => next(err))
        )
      }

      return result
    }
  }

  // Reflect method parameters
  private reflectArgs(
    instance: Controller,
    callback: Function,
    req: Request,
    res: Response,
    next: NextFunction
  ): any[] {
    const paramTypes = Reflect.getMetadata(PARAMTYPES_METADATA, instance, callback.name)
    const paramInjections = Reflect.getMetadata(INJECT_METADATA, instance, callback.name) || []

    return paramTypes.map((type, index) => {
      if (paramInjections[index]) {
        return this.resolveParam(paramInjections[index], req, res, next)
      }

      switch (index) {
        case 0: return req
        case 1: return res
        case 2: return next
        default: return undefined
      }
    })
  }
}

Middleware Source Code Implementation

MiddlewareConsumer Implementation:

// packages/core/src/middleware/middleware-consumer.ts
export class MiddlewareConsumer {
  private middleware: MiddlewareMetadata[] = []

  apply(middleware: Middleware): this {
    this.middleware.push({
      middleware,
      path: undefined,
      method: undefined
    })
    return this
  }

  forRoutes(routes: any[]): this {
    this.middleware.forEach(meta => {
      meta.path = this.reflectRoutes(routes)
    })
    return this
  }

  // Build middleware execution chain
  build(container: NestContainer): MiddlewareBuilder {
    return new MiddlewareBuilder(this.middleware, container)
  }
}

// Middleware execution order control
export class MiddlewareBuilder {
  constructor(
    private middleware: MiddlewareMetadata[],
    private container: NestContainer
  ) {}

  build(): (req: Request, res: Response, next: NextFunction) => void {
    return async (req, res, next) => {
      // 1. Execute middleware in order
      for (const meta of this.middleware) {
        const instance = this.container.getInstance(meta.middleware)
        await instance.use(req, res, next)
      }

      // 2. Execute next middleware or route handler
      next()
    }
  }
}

Exception Filter Source Code Implementation

ExceptionFilterFactory Implementation:

// packages/core/src/filters/exception-filters.ts
export class ExceptionFiltersContext {
  create(
    instance: Controller,
    callback: Function,
    module: string
  ): ExceptionFilter[] {
    // 1. Get exception filters on method
    const methodFilters = this.reflectMethodExceptionFilters(instance, callback)

    // 2. Get exception filters on class
    const classFilters = this.reflectClassExceptionFilters(instance)

    // 3. Merge filters and create instances
    return [...classFilters, ...methodFilters].map(filter => {
      return this.container.getInstance(filter)
    })
  }

  // Handle exception
  handle(exception: any, filters: ExceptionFilter[], response: Response) {
    // 1. Execute filters in order
    for (const filter of filters) {
      if (filter.catch(exception, response)) {
        break // Stop propagation if filter handles exception
      }
    }
  }
}

// Built-in exception filter
export class HttpExceptionFilter implements ExceptionFilter {
  catch(exception: HttpException, host: ArgumentsHost) {
    const ctx = host.switchToHttp()
    const response = ctx.getResponse<Response>()
    const request = ctx.getRequest<Request>()
    const status = exception.getStatus()

    response.status(status).json({
      statusCode: status,
      timestamp: new Date().toISOString(),
      path: request.url
    })
  }
}

NestJS Compilation and Startup Source Code

Application Startup Process Source Code Analysis

NestFactory.create Implementation:

// packages/core/src/nest-factory.ts
export class NestFactoryStatic {
  static async create<T extends INestApplication>(
    module: Type<any>,
    options?: NestApplicationOptions
  ): Promise<T> {
    // 1. Create application container
    const container = new NestContainer()

    // 2. Load root module
    const rootModule = await this.loadRootModule(module, container, options)

    // 3. Resolve module dependency graph
    await container.loadModules()

    // 4. Initialize modules
    await this.initializeModules(container, options)

    // 5. Create application instance
    const applicationRef = this.createApplicationRef(rootModule, options)

    // 6. Set up exception filters
    this.setupExceptionFilters(applicationRef, container)

    return applicationRef as T
  }

  // Load root module
  private static async loadRootModule(
    module: Type<any>,
    container: NestContainer,
    options?: NestApplicationOptions
  ): Promise<Module> {
    // 1. Reflect module metadata
    const { providers, controllers, imports } = await Module.reflectMetadata(module)

    // 2. Create module instance
    const rootModule = new Module(
      module,
      options?.scope || [],
      providers,
      controllers,
      imports,
      []
    )

    // 3. Register to container
    container.addModule(rootModule)

    return rootModule
  }
}

Module Loading and Initialization

Module Loading Process:

// packages/core/src/core/modules-loader.ts
export class ModulesLoader {
  async loadModules(container: NestContainer) {
    // 1. Get all registered modules
    const modules = Array.from(container.getModules().values())

    // 2. Topologically sort modules
    const sortedModules = this.topologicalSort(modules)

    // 3. Load modules in order
    for (const module of sortedModules) {
      await this.loadModule(module, container)
    }
  }

  // Load single module
  private async loadModule(module: Module, container: NestContainer) {
    // 1. Load imported modules
    await this.loadImports(module, container)

    // 2. Initialize providers
    await this.initializeProviders(module, container)

    // 3. Initialize controllers
    await this.initializeControllers(module, container)
  }
}

Dependency Injection Source Code Implementation

Reflection Metadata Parsing:

// packages/core/src/di/reflect-metadata.ts
export function reflectMetadata<T>(metadataKey: string, target: any, propertyKey?: string | symbol): T {
  if (propertyKey) {
    return Reflect.getMetadata(metadataKey, target, propertyKey)
  }
  return Reflect.getMetadata(metadataKey, target)
}

// PROVIDERS metadata example
function Controller() {
  return (target: Type<any>) => {
    Reflect.defineMetadata(CONTROLLERS, [target], target)
    Reflect.defineMetadata(PROVIDERS, [
      ...Reflect.getMetadata(PROVIDERS, target) || [],
      target
    ], target)
  }
}

Provider Resolution Process:

// packages/core/src/di/provider-resolver.ts
export class ProviderResolver {
  resolve<T>(
    provider: Provider,
    container: NestContainer,
    contextId = STATIC_CONTEXT
  ): Promise<T> {
    // 1. Handle different types of Provider
    if (isValueProvider(provider)) {
      return Promise.resolve(provider.useValue)
    } else if (isFactoryProvider(provider)) {
      return this.resolveFactoryProvider(provider, container, contextId)
    } else if (isClassProvider(provider)) {
      return this.resolveClassProvider(provider, container, contextId)
    }
  }

  // Resolve circular dependencies
  private async resolveWithCircularDependency(
    provider: ClassProvider,
    container: NestContainer,
    contextId: ContextId
  ): Promise<any> {
    const instanceWrapper = container.getInstanceWrapper(provider.provide)

    if (instanceWrapper.isResolved) {
      return instanceWrapper.instance
    }

    // Mark as resolving
    instanceWrapper.isResolving = true

    // Resolve dependencies
    const dependencies = this.reflectConstructorParams(provider.provide)
    const resolvedDependencies = await this.resolveDependencies(dependencies, container, contextId)

    // Instantiate
    instanceWrapper.instance = new provider.provide(...resolvedDependencies)
    instanceWrapper.isResolved = true
    instanceWrapper.isResolving = false

    return instanceWrapper.instance
  }
}

Request Lifecycle Source Code

Complete Request Processing Flow:

// packages/core/src/router/router-executor.ts
export class RouterExecutor {
  async execute(
    request: Request,
    response: Response,
    next: NextFunction,
    module: Module
  ) {
    // 1. Find matching route
    const route = this.findRoute(request, module)
    if (!route) {
      return next()
    }

    // 2. Get controller instance
    const instance = module.getInstance(route.controller)

    // 3. Create execution context
    const context = this.createContext(request, response, next)

    // 4. Execute middleware
    await this.executeMiddleware(module, route, context)

    // 5. Execute route handler
    const result = await this.executeHandler(instance, route, context)

    // 6. Handle response
    this.handleResponse(result, response)
  }

  // Execute middleware chain
  private async executeMiddleware(
    module: Module,
    route: Route,
    context: ExecutionContext
  ) {
    const middleware = module.getMiddlewareForRoute(route)
    for (const mw of middleware) {
      await mw.use(context.getRequest(), context.getResponse(), context.getNext())
    }
  }
}

Performance Optimization Source Code Implementation

Cache Implementation:

// packages/core/src/cache/cache-manager.ts
export class CacheManager {
  private cache = new Map<string, { value: any; ttl: number }>()

  async get<T>(key: string): Promise<T | undefined> {
    const entry = this.cache.get(key)
    if (!entry) return undefined

    // Check TTL
    if (entry.ttl < Date.now()) {
      this.cache.delete(key)
      return undefined
    }

    return entry.value
  }

  async set<T>(key: string, value: T, ttl: number): Promise<void> {
    this.cache.set(key, {
      value,
      ttl: Date.now() + ttl
    })
  }
}

// Using cache in dependency injection
export class CachedProviderResolver {
  constructor(private resolver: ProviderResolver, private cache: CacheManager) {}

  async resolve<T>(
    provider: Provider,
    container: NestContainer,
    contextId = STATIC_CONTEXT
  ): Promise<T> {
    const cacheKey = this.getCacheKey(provider, contextId)
    const cached = await this.cache.get<T>(cacheKey)
    if (cached) return cached

    const resolved = await this.resolver.resolve(provider, container, contextId)
    await this.cache.set(cacheKey, resolved, 60 * 1000) // Cache for 1 minute
    return resolved
  }
}

Lazy Loading Implementation:

// packages/core/src/core/lazy-loading.ts
export class LazyModuleLoader {
  private loadedModules = new Map<string, Module>()

  async load(module: Type<any>, container: NestContainer): Promise<Module> {
    const moduleName = this.getModuleName(module)

    if (this.loadedModules.has(moduleName)) {
      return this.loadedModules.get(moduleName)
    }

    // 1. Create module instance without immediately loading dependencies
    const moduleRef = new Module(
      module,
      [],
      [],
      [],
      [],
      []
    )

    // 2. Register to container without initialization
    container.addModule(moduleRef, true) // Mark as lazy-loaded

    // 3. Load dependencies on first access
    const proxy = this.createProxy(moduleRef, container)
    this.loadedModules.set(moduleName, proxy)

    return proxy
  }

  // Create proxy module
  private createProxy(module: Module, container: NestContainer): Module {
    return new Proxy(module, {
      get(target, prop) {
        if (prop === 'getInstance') {
          // Load dependencies on first access to getInstance
          container.initializeModule(target)
        }
        return Reflect.get(target, prop)
      }
    })
  }
}
Share your love