[NestJS Deep Dive] 01. NestFactory

Donghyung Ko
8 min readNov 15, 2022

Recently, I’ve started using NestJS at work. In the process, not understanding core parts of NestJS often made it difficult to NestJS fluently. So, I decided to take some time to take a close look at the NestJS framework for my personal studies and this is the first post of my NestJS deep dive series. As a first post of this series, I’d like to talk about NestFactory class, which is the entry point for all NestJS applications.

NestJS Deep Dive Series

  1. NestFactory
  2. @Module and DynamicModule
  3. InstnaceLoader and Injector

NestFactory

If you look at the NestJS official documentation, you can find the following code in the tutorial you’re facing for the first time.

import { NestFactory } from '@nestjs/core';
import { AppModule } from './app.module';
async function bootstrap() {
const app = await NestFactory.create(AppModule);
await app.listen(3000);
}

bootstrap();

You can find NestFactory reads `AppModule` to create an instance of `NestApplication` that contains all the logic of processing requests forwarded to the server. The goal of this posting is to understand what is happening in NestFactory.create().

NestFactory.create()

If you look at NestFactory.create(), it is implemented as follows. Let’s take a closer look at each line.

// packages/core/nest-factory.ts
public async create<T extends INestApplication = INestApplication>(
module: any,
serverOrOptions?: AbstractHttpAdapter | NestApplicationOptions,
options?: NestApplicationOptions,
): Promise<T> {
const [httpServer, appOptions] = this.isHttpServer(serverOrOptions)
? [serverOrOptions, options]
: [this.createHttpAdapter(), serverOrOptions];

const applicationConfig = new ApplicationConfig();
const container = new NestContainer(applicationConfig);
this.setAbortOnError(serverOrOptions, options);
this.registerLoggerConfiguration(appOptions);

await this.initialize(module, container, applicationConfig, httpServer);

const instance = new NestApplication(
container,
httpServer,
applicationConfig,
appOptions,
);
const target = this.createNestInstance(instance);
return this.createAdapterProxy<T>(target, httpServer);
}

HttpAdapter

If you look at the first three lines,NestFactory either uses the injected HttpAdatper as it is, or in most cases creates a new instance of HttpAdapter by default.

// packages/core/nest-factory.ts
export class NestFactory {
public async create<T extends INestApplication = INestApplication>(
module: any,
serverOrOptions?: AbstractHttpAdapter | NestApplicationOptions,
options?: NestApplicationOptions,
): Promise<T> {
// =======================================================
const [httpServer, appOptions] = this.isHttpServer(serverOrOptions)
? [serverOrOptions, options]
: [this.createHttpAdapter(), serverOrOptions];
// =======================================================

const applicationConfig = new ApplicationConfig();
const container = new NestContainer(applicationConfig);
this.setAbortOnError(serverOrOptions, options);
this.registerLoggerConfiguration(appOptions);

await this.initialize(module, container, applicationConfig, httpServer);

const instance = new NestApplication(
container,
httpServer,
applicationConfig,
appOptions,
);
const target = this.createNestInstance(instance);
return this.createAdapterProxy<T>(target, httpServer);
}
}

HttpAdapter corresponds to the class implementing the AbsctractHttpAdapter interface defined by NestJS Core. AbsctractHttpAdapter is an abstract class that implements part of the HttpServer interface that defines the required interfaces for processing http requests.

// packages/core/adapters/http-adapters.ts
export abstract class AbstractHttpAdapter<
TServer = any,
TRequest = any,
TResponse = any,
> implements HttpServer<TRequest, TResponse>
{
protected httpServer: TServer;

constructor(protected instance?: any) {}

public use(...args: any[]) {
return this.instance.use(...args);
}

public get(...args: any[]) {
return this.instance.get(...args);
}

public post(...args: any[]) {
return this.instance.post(...args);
}
...
abstract close();
abstract status(response: any, statusCode: number);
abstract reply(response: any, body: any, statusCode?: number);
abstract end(response: any, message?: string);
abstract render(response: any, view: string, options: any);
abstract redirect(response: any, statusCode: number, url: string);
}

One interesting part is that AbstractHttpAdapter relies on the injected instance property to implement core methods (ex, use, get, post). I think some of you have already noticed that the names of the methods are familiar.

Yes, this method matches the method name used by express to register middleware or handlers. In fact, NestFactory uses express internally to implement ExpressAdapter as its default HttpAdapter, which implements the AbstractHttpAdapter interface. From this point of view, NestJS can also be seen as a kind of “express-wrapper” framework. If you’re familiar with express, it’s a pleasure.

// packages/platform-express/adapters/express-adapter.ts
export class ExpressAdapter extends AbstractHttpAdapter {
...
constructor(instance?: any) {
super(instance || express()); // << express!!
}
}
// packages/core/nest-factory.ts
export class NestFactoryStatic {
...
private createHttpAdapter<T = any>(httpServer?: T): AbstractHttpAdapter {
const { ExpressAdapter } = loadAdapter(
'@nestjs/platform-express',
'HTTP',
() => require('@nestjs/platform-express'),
);
return new ExpressAdapter(httpServer);
}
}

ApplicationConfig

Let’s move on to the next line. NestFactory will then create an instance of ApplicationConfig

// packages/core/nest-factory.ts
export class NestFactory {
public async create<T extends INestApplication = INestApplication>(
module: any,
serverOrOptions?: AbstractHttpAdapter | NestApplicationOptions,
options?: NestApplicationOptions,
): Promise<T> {
const [httpServer, appOptions] = this.isHttpServer(serverOrOptions)
? [serverOrOptions, options]
: [this.createHttpAdapter(), serverOrOptions];

// =======================================================
const applicationConfig = new ApplicationConfig();
// =======================================================

const container = new NestContainer(applicationConfig);
this.setAbortOnError(serverOrOptions, options);
this.registerLoggerConfiguration(appOptions);

await this.initialize(module, container, applicationConfig, httpServer);

const instance = new NestApplication(
container,
httpServer,
applicationConfig,
appOptions,
);
const target = this.createNestInstance(instance);
return this.createAdapterProxy<T>(target, httpServer);
}
}

ApplicationConfig corresponds to a relatively simple data object that stores the global middleware used by NestJS. Although most of them are initially initialized to empty values, all global middleware is registered internally in ApplicationConfig later on.

// packages/core/application-config.ts
export class ApplicationConfig {
private globalPrefix = '';
private globalPrefixOptions: GlobalPrefixOptions<ExcludeRouteMetadata> = {};
private globalPipes: Array<PipeTransform> = [];
private globalFilters: Array<ExceptionFilter> = [];
private globalInterceptors: Array<NestInterceptor> = [];
private globalGuards: Array<CanActivate> = [];
private versioningOptions: VersioningOptions;
...
}

NestContainer

Next, NestFactory creates NestContainer that plays a key role in the dependency injection (often called, DI) in NestJS. NestContainer is an object that stores Module classes that is essential for NestApplication to operate.

// packages/core/nest-factory.ts
export class NestFactory {
public async create<T extends INestApplication = INestApplication>(
module: any,
serverOrOptions?: AbstractHttpAdapter | NestApplicationOptions,
options?: NestApplicationOptions,
): Promise<T> {
const [httpServer, appOptions] = this.isHttpServer(serverOrOptions)
? [serverOrOptions, options]
: [this.createHttpAdapter(), serverOrOptions];

const applicationConfig = new ApplicationConfig();

// =======================================================
const container = new NestContainer(applicationConfig);
// =======================================================

this.setAbortOnError(serverOrOptions, options);
this.registerLoggerConfiguration(appOptions);

await this.initialize(module, container, applicationConfig, httpServer);

const instance = new NestApplication(
container,
httpServer,
applicationConfig,
appOptions,
);
const target = this.createNestInstance(instance);
return this.createAdapterProxy<T>(target, httpServer);
}
}

NestContainer internally has Module, ModuleTokenFactory, ModuleCompiler, and ModulesContainer. NestContainer will then register the module via DependenciesScanner. More details on this will be covered later.

// packages/core/injector/factory.ts
export class NestContainer {
private readonly globalModules = new Set<Module>();
private readonly moduleTokenFactory = new ModuleTokenFactory();
private readonly moduleCompiler = new ModuleCompiler(this.moduleTokenFactory);
private readonly modules = new ModulesContainer();
private readonly dynamicModulesMetadata = new Map<string, Partial<DynamicModule>>();
...

public addProvider(
provider: Provider,
token: string,
): string | symbol | Function {
const moduleRef = this.modules.get(token);
if (!provider) {
throw new CircularDependencyException(moduleRef?.metatype.name);
}
if (!moduleRef) {
throw new UnknownModuleException();
}
return moduleRef.addProvider(provider);
}
}

Module

The Module class is an object that defines all dependencies (ex, Provider, Controller) contained in a single module. Modules that we typically register with @Module decoration or through the forRoot() method as DynamicModule are managed as Module classes within the NestJS framework.

// packages/core/injector/module.ts
export class Module {
private readonly _id: string;
private readonly _imports = new Set<Module>();
private readonly _providers = new Map<
InstanceToken,
InstanceWrapper<Injectable>
>();
private readonly _injectables = new Map<
InstanceToken,
InstanceWrapper<Injectable>
>();
private readonly _middlewares = new Map<
InstanceToken,
InstanceWrapper<Injectable>
>();
private readonly _controllers = new Map<
InstanceToken,
InstanceWrapper<Controller>
>();
}

ModuleTokenFactory

ModuleTokenFactory is a simple class that generates a token used as a key when registering and retrieving modules.

// packages/core/injector/module-token-factory.ts
export class ModuleTokenFactory {
public create(
metatype: Type<unknown>,
dynamicModuleMetadata?: Partial<DynamicModule> | undefined,
): string {
const moduleId = this.getModuleId(metatype);
const opaqueToken = {
id: moduleId,
module: this.getModuleName(metatype),
dynamic: this.getDynamicMetadataToken(dynamicModuleMetadata),
};
return hash(opaqueToken, { ignoreUnknown: true });
}
...
}

ModuleCompiler

NestContainer separately manages the dependency related information of DynamicModule. For this purpose, ModuleCompiler can be considered as a utility class that performs the function of properly parsing the information in each module.

// packages/core/injector/compiler.ts
export class ModuleCompiler {
public async compile(
metatype: Type<any> | DynamicModule | Promise<DynamicModule>,
): Promise<ModuleFactory> {
const { type, dynamicMetadata } = this.extractMetadata(await metatype);
const token = this.moduleTokenFactory.create(type, dynamicMetadata);
return { type, dynamicMetadata, token };
}
...
}

ModulesContainer

ModulesContainer is a relatively simple class of Map<string, Module> type that stores all modules. The key value will use the token generated by ModuleTokenFactory

// packages/core/injector/modules-container.ts
export class ModulesContainer extends Map<string, Module> {
private readonly _applicationId = uuid();

get applicationId(): string {
return this._applicationId;
}
}

initialize()

Next, NestFactory calls the initialize() method after a simple setup. The core role of the initialize() method can be largely divided into the module registration process by DependenciesScanner and the dependency object instance creation done by InstanceLoader.

// packages/core/nest-factory.ts
export class NestFactory{
...
private async initialize(
module: any,
container: NestContainer,
config = new ApplicationConfig(),
httpServer: HttpServer = null,
) {
const instanceLoader = new InstanceLoader(container);
const metadataScanner = new MetadataScanner();
const dependenciesScanner = new DependenciesScanner(
container,
metadataScanner,
config,
);
container.setHttpAdapter(httpServer);

const teardown = this.abortOnError === false ? rethrow : undefined;
await httpServer?.init();
try {
this.logger.log(MESSAGES.APPLICATION_START);

await ExceptionsZone.asyncRun(
async () => {
await dependenciesScanner.scan(module);
await instanceLoader.createInstancesOfDependencies();
dependenciesScanner.applyApplicationProviders();
},
teardown,
this.autoFlushLogs,
);
} catch (e) {
this.handleInitializationError(e);
}
}
}

DependenciesScanner

The most important role of DependenciesScanner is to traverse the module tree and register the modules in NestContaine. In addition, DependenciesScanner registers relationships between modules (edge), manages information about dependencies linked to modules such as providers and calculates the depth of modules node used when traversing modules topological order inside NestApplication

// packages/core/scanner.ts
export class DependenciesScanner {
...
public async scan(module: Type<any>) {
await this.registerCoreModule(); // register InternalCoreModule to NestContainer
await this.scanForModules(module); // registers all modules in NestContainer by traversing the Module Tree
await this.scanModulesForDependencies(); // add information related to the dependencies (imports, providers, controllers, exports) of registered modules through metadata parsing
this.calculateModulesDistance(); // register the depth information required to sort modules in topological order

this.addScopedEnhancersMetadata(); // register provider's Scope-related metadata additionally to the controller
this.container.bindGlobalScope(); // adding connection information to modules importing Global Modules
}

InstanceLoader

InstanceLoader is responsible for sequentially creating prototype objects and instances of dependencies such as Provider, Injectable and Controller of modules registered in NestContainer. Creating a prototype is quite simple, but creating instances with complex dependency injection is relatively difficult. This function is executed by an internal class called Injector. Injector plays a key role in dependency injection, including determining the hierarchy of dependent objects, using callbacks to coordinate the order of instances being created, and properly inject them to create instances of all dependent objects. There is a lot of content to cover in this post, so we will find out more about Injector in other posts later.

// packages/core/injector/instance-loader.ts
export class InstanceLoader {
protected readonly injector = new Injector();

public async createInstancesOfDependencies(
modules: Map<string, Module> = this.container.getModules(),
) {
this.createPrototypes(modules);
await this.createInstances(modules);
}
...
}

NestApplication

It’s finally the final step. NestFactory creates an instance of NestApplication after completing both the module and the dependency object instance registration process. Afterwards, the role of NestFactory ends by wrapping it with Proxy object for some functions such as error handling, method chaining, fallback and so on.

// packages/core/nest-factory.ts
export class NestFactory {
public async create<T extends INestApplication = INestApplication>(
module: any,
serverOrOptions?: AbstractHttpAdapter | NestApplicationOptions,
options?: NestApplicationOptions,
): Promise<T> {
const [httpServer, appOptions] = this.isHttpServer(serverOrOptions)
? [serverOrOptions, options]
: [this.createHttpAdapter(), serverOrOptions];

const applicationConfig = new ApplicationConfig();
const container = new NestContainer(applicationConfig);
this.setAbortOnError(serverOrOptions, options);
this.registerLoggerConfiguration(appOptions);

await this.initialize(module, container, applicationConfig, httpServer);

// =======================================================
const instance = new NestApplication(
container,
httpServer,
applicationConfig,
appOptions,
);
const target = this.createNestInstance(instance);
return this.createAdapterProxy<T>(target, httpServer);
// =======================================================
}
}

In this post, we briefly covered how NestApplication, which contains all service logic is being created by NestFactory. In the following post, I’m going to talk more about Modulesand h ow NestApplication handles requests delivered to servers in more detail.

References

--

--