NestJS 框架入门:从工厂模式到模块化架构

当你第一次打开一个 NestJS 项目,看到的不是一坨 app.js,而是一堆 @Module、@Controller、@Injectable 装饰器时,可能会觉得"这也太绕了吧"。但一旦理解了它背后的设计模式,你会发现这种结构恰恰是大型项目保持可维护性的关键。

NestJS 是什么

NestJS 是一个用于构建 Node.js 后端服务的框架,默认使用 TypeScript。它的设计目标是企业级------当你面对的是几十个接口、多个业务模块、团队协作开发时,NestJS 的模块化思想能让代码不至于变成一团乱麻。

它底层基于 Express(也可以切到 Fastify),但在 Express 之上提供了一套完整的架构约定,核心是三件事:

特性 作用 对应概念
模块化 按业务拆分独立单元 @Module()
依赖注入 自动装配 Service 到 Controller @Injectable()
装饰器 给类动态叠加路由、校验等能力 @Get() @Controller() 等

在深入这些之前,先从一个最根本的设计模式说起------工厂模式。


工厂模式:蜜雪冰城的启示

NestJS 的入口函数 NestFactory.create() 本质上就是工厂模式的运用。要理解它,先看一个更生活化的例子。

一个奶茶工厂

想象你去蜜雪冰城点单。你不需要知道冰激凌怎么做的、柠檬茶怎么泡的------你只需要跟前台说一句"来一个冰激凌",工厂内部负责生产,你拿到成品就行。

用代码表达就是这样的:

js 复制代码
// 产品类:每个产品都有相同的接口(show 方法)
class IceCream {
    constructor() {
        this.name = '冰激凌';
        this.price = 3;
    }
    show() {
        console.log(this.name, this.price + '元');
    }
}

class LemonTea {
    constructor() {
        this.name = '柠檬茶';
        this.price = 4;
    }
    show() {
        console.log(this.name, this.price + '元');
    }
}

class MilkTea {
    constructor() {
        this.name = '珍珠奶茶';
        this.price = 8;
    }
    show() {
        console.log(this.name, this.price + '元');
    }
}

// 工厂类:对外只暴露一个 create 方法
class MixueFactory {
    static create(type) {
        switch (type) {
            case 'ice':   return new IceCream();
            case 'lemon': return new LemonTea();
            case 'milk':  return new MilkTea();
        }
    }
}

// 消费者:不需要了解工厂内部细节,直接调用
const drink1 = MixueFactory.create('ice');
drink1.show();   // 冰激凌 3元

const drink2 = MixueFactory.create('lemon');
drink2.show();   // 柠檬茶 4元

const drink3 = MixueFactory.create('milk');
drink3.show();   // 珍珠奶茶 8元

工厂模式解决了什么

没有 工厂 有工厂
调用者需要知道每个产品类的名字和构造方式 调用者只需知道工厂和一个类型参数
产品类一多,调用代码就混乱 新增产品只需改工厂内部,调用代码不变
产品类和调用代码强耦合 工厂屏蔽了内部细节,实现解耦

核心思想:面向接口编程,而不是面向实现编程 。所有产品都实现了 show() 方法,工厂返回哪个具体类,调用者不需要关心。

NestFactory:NestJS 的工厂

理解了蜜雪冰城,再看 NestJS 的入口就顺理成章了:

ts 复制代码
import { NestFactory } from '@nestjs/core';
import { AppModule } from './app.module';

async function bootstrap() {
    // NestFactory 就是那个"工厂"
    // create(AppModule) 就像 MixueFactory.create('ice')
    // 你传入一个模块,工厂帮你创建出一个完整的应用实例
    const app = await NestFactory.create(AppModule);
    
    // 启动 HTTP 服务,监听 3000 端口
    await app.listen(process.env.PORT ?? 3000);
}

bootstrap();

NestFactory.create(AppModule) 做的事情,和 MixueFactory.create('ice') 是同一个思路:你把需求交给工厂,工厂负责组装和返回实例。NestJS 可以构建的远不止 HTTP 服务------它还支持 WebSocket、微服务、gRPC 等,NestFactory 就是那个统一的生产入口。


装饰器模式:给类叠加能力

NestJS 的代码里到处都是 @ 开头的语法,这就是装饰器。

装饰器的本质是:不修改原有类的代码,动态给它叠加额外功能。

ts 复制代码
@Controller('todos')          // 给这个类贴上"控制器"标签,路由前缀是 /todos
export class TodosController {
    @Get()                     // 给这个方法贴上"处理 GET 请求"标签
    findAll() { ... }

    @Post()                    // 给这个方法贴上"处理 POST 请求"标签
    create() { ... }
}
传统 Express 写法 NestJS 装饰器写法
app.get('/todos', (req, res) => { ... }) @Get() 贴在方法上,路由自动注册
路由和逻辑代码混在一起 路由声明和业务代码分离,可读性强
没有类型约束 TypeScript 类型检查全程保驾护航

NestJS 把装饰器用到了极致:@Module() 定义模块、@Controller() 定义控制器、@Injectable() 标记可注入服务、@Get() @Post() @Delete() 定义路由方法、@Param() @Body() 提取请求参数。每个装饰器各司其职,把一个普通类变成了功能完整的后端组件。

注意:TypeScript 默认不开启装饰器支持。NestJS 项目的 tsconfig.json 中必须配置 "experimentalDecorators": true 和 "emitDecoratorMetadata": true,这也是 nest new 脚手架自动帮你做好的。


模块化架构:Module → Controller → Service

NestJS 的核心架构可以用一句话概括:

ini 复制代码
AppModule(根模块)
  ├── imports: [其他子模块...]
  ├── controllers: [控制器列表]
  └── providers: [服务列表]

三层职责划分

层 文件约定 职责 装饰器
Module xx.module.ts 组装模块,声明依赖关系 @Module()
Controller xx.controller.ts 接收请求、参数校验、返回响应 @Controller()
Service xx.service.ts 业务逻辑、数据处理 @Injectable()

这就是经典 MVC 思想在 NestJS 中的映射:

  • M(Model/Service) :数据服务和业务逻辑层,通过 @Injectable() 标记,可被自动注入
  • C(Controller):控制器层,处理 HTTP 请求的入口,校验参数后把业务交给 Service
  • V(View):NestJS 是纯后端框架,视图层由前端负责

根模块长什么样

ts 复制代码
import { Module } from '@nestjs/common';
import { AppController } from './app.controller';
import { AppService } from './app.service';
import { TodosModule } from './todos/todos.module';

@Module({
    imports: [TodosModule],       // 依赖的子模块
    controllers: [AppController], // 本模块的控制器
    providers: [AppService],      // 本模块的服务
})
export class AppModule {}

@Module() 装饰器接收一个配置对象,三个关键字段:

  • imports :声明本模块依赖哪些其他模块(比如 TodosModule)
  • controllers:注册本模块的控制器
  • providers:注册本模块的服务(Service)

依赖注入:Service 自动到 Controller

这是 NestJS 最"魔法"的地方。在 Controller 中:

ts 复制代码
@Controller()
export class AppController {
    // 没有手动 new AppService(),直接声明就拿到了
    constructor(private readonly appService: AppService) {}
    
    @Get()
    getHello(): string {
        return this.appService.getHello();
    }
}

你没有写 new AppService(),但 this.appService 就能用了。这是因为:

  1. AppService 用 @Injectable() 标记为可注入
  2. AppModule 的 providers 中注册了 AppService
  3. NestJS 的 IoC 容器在创建 AppController 时,自动实例化 AppService 并注入到构造函数中
手动实例化 依赖注入
const service = new AppService() 每处都要写 容器自动创建并注入
Service 换实现类,所有调用处都要改 只改 Module 注册,调用处不变
无法轻松 mock 做单元测试 测试时可替换为 mock 实现

项目目录结构一览

一个标准的 NestJS 项目结构如下:

ruby 复制代码
hello/
├── src/
│   ├── main.ts                  # 入口文件:创建应用、启动服务
│   ├── app.module.ts            # 根模块:组装所有模块
│   ├── app.controller.ts        # 根控制器:处理 / 路由
│   ├── app.service.ts           # 根服务:返回 Hello World
│   └── todos/                   # 业务模块(独立目录)
│       ├── todos.module.ts      # 模块定义
│       ├── todos.controller.ts  # 控制器:RESTful 路由
│       └── todos.service.ts     # 服务:CRUD 业务逻辑
├── test/
│   └── app.e2e-spec.ts          # E2E 测试
├── package.json
├── tsconfig.json                # TypeScript 配置(开启装饰器)
└── nest-cli.json                # NestJS CLI 配置

关键约定:

  • 入口 :main.ts 负责引导启动,调用 NestFactory.create(AppModule) 创建应用
  • 模块 :每个业务功能是一个独立目录,包含 module.ts、controller.ts、service.ts 三件套
  • 组装 :根模块 app.module.ts 通过 imports 引入所有子模块,形成依赖树

从零启动一个 NestJS 项目

bash 复制代码
# 全局安装 NestJS CLI
npm i -g @nestjs/cli

# 创建新项目
nest new hello

# 进入项目目录
cd hello

# 开发模式运行(文件变更自动重启)
npm run start:dev

打开浏览器访问 http://localhost:3000,看到 Hello World! 就说明项目已经跑起来了。

package.json 中几个关键命令:

命令 作用
npm run start 普通启动
npm run start:dev 开发模式,热重载
npm run build 编译到 dist/ 目录
npm run start:prod 生产模式,运行编译后的 dist/main.js
npm test 运行单元测试
npm run test:e2e 运行端到端测试

小结

NestJS 的架构设计并不复杂,核心就是三个设计模式的组合运用:

  1. 工厂模式 :NestFactory.create() 统一创建应用实例,屏蔽内部组装细节
  2. 装饰器模式 :@Module @Controller @Injectable 等装饰器给类叠加能力,不改原有代码
  3. 依赖注入:Service 不需要手动实例化,由 IoC 容器自动注入到需要的地方

理解了这套架构之后,再去看任何 NestJS 项目,你会发现它们都遵循同一个模式:入口创建应用 → 模块组织结构 → 控制器处理请求 → 服务处理业务。掌握了这个套路,就等于拿到了 NestJS 的万能钥匙。

下一篇,我们会用一个完整的 Todos CRUD 模块,把这套架构从纸面概念变成可运行的代码。

相关推荐
金銀銅鐵3 小时前
[Java] 用 GUI 展示 EnumSet 增减元素时的内部变化(上)
java·后端·ai编程
念何架构之路3 小时前
go-grpc设计思想
开发语言·后端·golang
system_error_x4 小时前
SpringBoot 集成 Thymeleaf 简洁入门
java·spring boot·后端
步行cgn5 小时前
Spring MVC 视图机制的核心接口与类
java·开发语言·后端·spring·mvc
码事漫谈6 小时前
AI 读你的提示词,其实很没耐心
后端
用户8356290780517 小时前
如何使用 Python 格式化 Excel 单元格中的长文本
后端·python
我命由我123457 小时前
Python 开发 - Python 的 pathlib
java·开发语言·后端·python·学习·java-ee·学习方法
IT_陈寒7 小时前
Java性能优化完全指南
前端·人工智能·后端
电子IT笔记7 小时前
对接群晖NAS,上传和删除踩坑记录
前端·后端
Mk598787 小时前
P4:把 Agent 的记忆搬进 Redis —— AgentScope-Java 工程化实战
后端