跪拜 Guibai
← Back to the summary

NestFactory Is a Factory: How Nest.js Bootstraps from a Single create() Call

Factory Pattern and the Core Ideas of Nest.js — From Mixue Ice Cream & Tea to Enterprise Architecture

Through the real story of Mixue Ice Cream & Tea, this article helps you understand the design philosophy of the factory pattern, and uses it to dive into Nest.js modules, controllers, services, dependency injection, and decorators, so you can thoroughly grasp how a Nest application "comes to life."


1. Why Do We Need Design Patterns? Starting from "Ordering Milk Tea"

A note says:

"If you want milk tea, you don't make it yourself (no procedural code). You go to Mixue."

This is actually a simple embodiment of interface-oriented programming. As consumers, we don't care about the milk tea production process (how much tea, how much sugar); we only need to know that the factory called "Mixue" can provide the product we want.

In the code world, if we directly use new IceCream(), new LemonTea(), every place that uses them must be modified whenever a new product is added. This is tight coupling.

The Factory Pattern emerged to solve this: encapsulate object creation logic into a factory class. The user only needs to tell the factory "what type I want," and the factory returns the corresponding instance. This is exactly what the MixueFactory code in the note demonstrates.

class MixueFactory {
    static create(type) {
        switch(type) {
            case 'ice': return new IceCream();
            case 'lemon': return new LemonTea();
            case 'milk': return new MilkTea();
        }
    }
}

Benefits this brings:

Key sentence from the note:

"Developers only need to call MixueFactory.create('type'). Since every class in the factory implements the same show interface, classes produced by the factory can be called directly with confidence."


2. The Factory Pattern in Nest.js: NestFactory

In Nest.js, we see this code in main.ts:

const app = await NestFactory.create(AppModule);

NestFactory is a factory class responsible for creating the entire Nest application instance. We don't need to care about how it internally initializes middleware or configures the underlying HTTP server (Express/Fastify); we just need to pass in the root module AppModule to get a runnable application object.

This perfectly corresponds to the Mixue story:


3. Nest's Modularity — Decoding the @Module Decorator

The note says:

"A Module is a whole, the most common backend MVC pattern. A file with thousands of lines of code organizes the controller and service layer CRUD."

Nest uses the @Module() decorator to organize controllers and services into a logical unit. Let's look at the app.module.ts code:

@Module({
  imports: [], // Depend on the outside world?
  controllers: [AppController], // Controller: validation, simple logic
  providers: [AppService], // data service: complex business logic
})
export class AppModule {}

In-depth interpretation of the comments:


4. Controllers and Services — The Magic of Dependency Injection

In app.controller.ts:

@Controller()
export class AppController {
  constructor(private readonly appService: AppService) {}
  // ...
}

The underlying logic of this line of code:

  1. When TypeScript compiles, the type information of the constructor parameters is preserved in metadata (design:paramtypes).
  2. At startup, Nest scans all @Controller() classes and discovers that AppController needs AppService.
  3. It checks the IoC container to see if an instance of AppService already exists (since AppService is marked with @Injectable() and registered in providers).
  4. If it exists, it is injected directly; if not, it is created first and then injected.
  5. After injection, this.appService can be used directly.

This is the essence of Dependency Injection (DI): Inversion of Control — instead of us actively using new, the container "delivers" the dependency to us. This greatly reduces coupling and facilitates unit testing (mock objects can be injected).


5. The Decorator Pattern — What Exactly Is the @ Symbol?

The note says:

"The decorator pattern dynamically adds extra functionality to an object without modifying the original object."

But @Controller, @Module, @Injectable in Nest are not the classic decorator pattern (runtime wrapping). They are TypeScript decorators, essentially a higher-order function that executes when the class is defined, used to add metadata to the class.

For example, @Module({...}) attaches configuration information to the AppModule class. Nest reads this metadata at startup to know how to assemble the application.

The real application of the decorator pattern in Nest is Interceptors and Guards, which wrap route handler functions at runtime, dynamically adding features like logging and permissions. We will dive into this part in a follow-up article.


6. Understanding the Application Startup Process from main.ts

async function bootstrap() {
  const app = await NestFactory.create(AppModule);
  await app.listen(process.env.PORT ?? 3000);
}
bootstrap();

Process breakdown:

  1. NestFactory.create(AppModule) — The factory creates the application instance.
  2. Internally, it reads the metadata of AppModule and recursively resolves all dependent modules (like TodosModule).
  3. It initializes all controllers and services, and establishes route mappings.
  4. It starts the underlying HTTP server, listening on the specified port.
  5. At this point, our API is accessible.

7. Summary

Concept Corresponding Note/Code My Understanding
Factory Pattern Mixue, NestFactory Encapsulates object creation, decouples caller from implementation class
Module @Module({imports, controllers, providers}) A unit for organizing code, like a company department
Controller @Controller, responsible for routing and parameter extraction Only does dispatching, no business logic
Service @Injectable, responsible for business logic Can be injected anywhere needed
Dependency Injection constructor(private readonly xxxService) Container automatically provides dependencies, no manual new needed
Decorator @ symbol, provides metadata Tags classes/methods for the framework to read