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. AppModuleproviders 中注册了 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.tscontroller.tsservice.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 模块,把这套架构从纸面概念变成可运行的代码。

相关推荐
灯澜忆梦1 小时前
【基于GO的Web开发4】html/template 模板语法
后端·golang·html
一只叫煤球的猫1 小时前
Spring AI 2.0 源码解析(三):ChatClient 的 Fluent API 如何构建请求?
java·后端·面试
掘金者阿豪2 小时前
数据库迁移工具选了半年,最后发现决策的起点错了
后端
卷无止境2 小时前
FastAPI查询参数模型:把散落的参数收拢成一个整齐的盒子
后端·python
gis开发之家3 小时前
Spring Boot 4 深度解析——参数接收大全:@RequestParam、@PathVariable、@RequestBody
java·spring boot·后端·spring
NutShell Wang3 小时前
Rust 1.97 实战迁移:v0 符号重整、Cargo 警告治理与位运算新 API
人工智能·后端·性能优化·rust·vibe coding
阑梦清川3 小时前
零成本把笔记转成双人播客:WorkBuddy + 腾讯云 TTS 完整教程
后端
赫媒派3 小时前
Go 1.27 来了:泛型方法补齐,JSON 提速不踩坑
后端·go·敏捷开发
不ok哥男人3 小时前
C# Roslyn 编译器平台实战:源生成器、分析器与代码修补
后端