一文搞懂 NestJS 后端框架:工厂模式、模块化与装饰器

一文搞懂 NestJS 后端框架:工厂模式、模块化与装饰器

很多前端同学一听"NestJS",第一反应是:"这不就是 Next.js 吗?"------不是! Next.js 是 React 全家桶里的全栈框架 ,而 NestJS 是 Node 的纯后端企业级开发框架 。这篇文章用大白话 + 一个能跑通的后端 Demo,把 NestJS 最核心的三个思想------工厂模式、高度模块化、装饰器模式一次讲透。重点用"蜜雪冰城"这个例子把设计模式讲明白。

前言

最近开始学后端,老师上来就扔了一句话:"NestJS 是 node 的纯后端企业级开发框架,默认 TypeScript,全面模块化思想。"我当场就懵了:"企业级"是什么?"模块化"又是什么?模块化就算了,怎么还有设计模式? 后来跟着老师一步步写代码,发现这框架里到处是之前学前端时听过的设计模式------尤其是工厂模式 ,NestJS 最核心的 NestFactory 就是工厂模式的活教材。踩了不少坑,写下来希望能帮到和我一样刚入门的同学。

你将会收获:

  • 🎯 后端开发到底做些什么(API、系统集成、微服务)
  • 🔑 NestJS 和 Next.js 到底是不是一回事
  • ⚡ 设计模式是什么,工厂模式为什么是"23 种模式里最重要的一种"
  • 🍦 蜜雪冰城怎么教你写工厂模式(MixueFactory 完整可跑 Demo)
  • 🏭 NestFactory.create(AppModule) 就是工厂模式
  • 🧱 高度模块化:ControllerService 的分工约定
  • 🏷️ 装饰器模式:@ 符号背后的魔法
  • 🐛 两个真实踩坑:package.json 被覆盖、pnpm 11 拦截构建脚本

技术栈: NestJS(11)+ TypeScript + pnpm

目录

  • 一、后端开发到底做些什么?
  • [二、NestJS 是什么?和 Next.js 别搞混](#二、NestJS 是什么?和 Next.js 别搞混 "#%E4%BA%8Cnestjs-%E6%98%AF%E4%BB%80%E4%B9%88%E5%92%8C-nextjs-%E5%88%AB%E6%90%9E%E6%B7%B7")
  • 三、装环境,跑起来看目录
  • 四、工厂模式:最重要的设计模式
  • [五、NestFactory 就是工厂模式](#五、NestFactory 就是工厂模式 "#%E4%BA%94nestfactory-%E5%B0%B1%E6%98%AF%E5%B7%A5%E5%8E%82%E6%A8%A1%E5%BC%8F")
  • [六、高度模块化:App 拆成 Modules](#六、高度模块化:App 拆成 Modules "#%E5%85%AD%E9%AB%98%E5%BA%A6%E6%A8%A1%E5%9D%97%E5%8C%96app-%E6%8B%86%E6%88%90-modules")
  • [七、装饰器模式:@ 符号的魔法](#七、装饰器模式:@ 符号的魔法 "#%E4%B8%83%E8%A3%85%E9%A5%B0%E5%99%A8%E6%A8%A1%E5%BC%8F-%E7%AC%A6%E5%8F%B7%E7%9A%84%E9%AD%94%E6%B3%95")
  • 八、一次请求完整走一遍
  • [九、真实踩坑 2 个](#九、真实踩坑 2 个 "#%E4%B9%9D%E7%9C%9F%E5%AE%9E%E8%B8%A9%E5%9D%91-2-%E4%B8%AA")
  • [十、面试高频 4 问](#十、面试高频 4 问 "#%E5%8D%81%E9%9D%A2%E8%AF%95%E9%AB%98%E9%A2%91-4-%E9%97%AE")
  • 总结

一、后端开发到底做些什么?

先搞清楚一个基础问题:后端开发是干嘛的? 老师给了一张图,我记下来了:

bash 复制代码
前端(浏览器 / App)
     │ 发请求:GET /api/xxx
     ▼
后端(服务器)
     ├── 提供 API 接口        → 给前端返回数据(JSON)
     ├── 系统集成、并发        → 底层服务、AI Infra
     └── 微服务              → 把一个大系统拆成一堆小服务
     ▼
数据库(MySQL / Redis ...)

后端干的活,概括起来就三类:

类型 大白话 例子
提供 API 接口 前端要数据?我给你 用户登录、商品列表、订单提交
系统集成 / 底层服务 多个系统对接、扛住高并发 支付回调、消息队列、AI Infra
微服务 大系统拆成小团队各自维护的小服务 订单服务、支付服务、库存服务

💡 一句话记住:后端 = 给前端供数据 + 扛住并发 + 拆微服务。

那用什么工具写后端?老师选择了 NestJS


二、NestJS 是什么?和 Next.js 别搞混

先踩个坑:NestJS ≠ Next.js

这俩名字太像了,我一开始直接当成一个东西:

Next.js NestJS
出身 React 生态 Node 生态(Angular 风格)
定位 全栈框架(前端 + 部分后端) 纯后端企业级框架
语言 JS/TS 都可以 默认 TypeScript
核心思想 SSR、文件即路由 模块化、设计模式

⚠️ 面试考点 :面试官特别喜欢问"Next.js 和 NestJS 的区别"。答:Next.js 是 React 的全栈框架(服务端渲染为主),NestJS 是 Node 的纯后端框架,两者不在一个赛道。

NestJS 的核心卖点:全面模块化

NestJS 拿什么和"裸 Node"或者 Express 比?打个比方:

  • 裸写 Express:像是让你从一块木头开始做家具------啥工具都得自己找,代码全堆在一个文件里,几百上千行是常事。
  • NestJS:像是给你一间已经划分好房间的工厂------每个模块(Module)各司其职,照着规范往里填东西就行。
markdown 复制代码
一个文件几千行代码 ❌
        ↓ 模块化
App(根模块)
 ├── Controller(控制器)  → 接请求、做校验、简单逻辑
 └── Service(服务)       → 复杂业务、CRUD、SQL

💡 一句话记住:NestJS = 用"模块化 + 设计模式"把后端代码组织成有纪律的团队。


三、装环境,跑起来看目录

安装很简单,三行命令:

bash 复制代码
npm i -g @nestjs/cli     # 安装脚手架
nest new hello           # 生成一个叫 hello 的项目
nest start               # 启动(开发用 nest start --watch)

装完的目录长这样:

ruby 复制代码
hello/
├── src/                  # 源码都在这里
│   ├── main.ts           # 入口文件:启动应用
│   ├── app.module.ts     # 根模块:总调度
│   ├── app.controller.ts # 控制器:接收请求
│   └── app.service.ts    # 服务:处理业务
├── package.json
└── tsconfig.json

📌 记忆点:main.ts 是门卫,app.module.ts 是董事长,controller 是前台接待,service 是干活的技术工。

接下来我们要干一件很有仪式感的事------看看 NestJS 背后到底用了什么设计模式。而它最核心的那个,就是工厂模式。


四、工厂模式:最重要的设计模式

先讲痛点:一个企业这么多产品,开发者怎么记得住?

老师上课前,先抛了一个问题:

蜜雪冰城有冰激凌、柠檬水、珍珠奶茶......几百种产品。每种产品都是一个类,每个类都实现了相同的方法(比如 show())。企业这么大,开发者怎么记得住这么多类? 每次要个产品,难道还得自己 new 出对应的类吗?

想象一下,如果没有"工厂",你点单是这样:

javascript 复制代码
// ❌ 没有工厂:每次点单都要自己 new,还得记住类名
// 冰激凌是 IceCream、柠檬水是 LemonTea、奶茶是 MilkTea...
// 100 种产品 = 记住 100 个类名,还容易拼错
const drink = new IceCream();   // 这个类叫啥来着?IceCream?IceCreamMachine?
drink.show();

太痛苦了。所以聪明的办法是------找一个"工厂"来帮你点单

工厂模式:把"创建"和"使用"解耦

工厂模式的核心思想就一句话:

调用方不需要知道"我要的东西是哪个类、怎么 new 出来的",只需要告诉工厂"我要什么"。

这就是你在蜜雪冰城做的事:你不会自己去后厨做奶茶,你只需要对着店员说一句"要一杯柠檬水"。

javascript 复制代码
// 蜜雪冰城产品之一  冰激凌
// 企业, 很多的产品, 每一种产品都实现了相同的接口(方法),
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}元`)
  }
}

// 工厂类:你不需要了解工厂里面那么多类的实现细节,
// 只要直接和工厂类打交道就好了
class MixueFactory {
  static create(type) {
    switch(type) {
      case 'ice': 
        return new IceCream()
      case 'lemon':
        return new LemonTea()
      case 'milk':
        return new MilkTea()
    }
  }
}

调用方变成什么样了?从头到尾没见过 new 和具体的类名:

javascript 复制代码
// 调用方:我只说我"要什么",剩下的交给工厂
const drink1 = MixueFactory.create('ice');     // 我要冰激凌
drink1.show();                                 // 冰激凌 3元

const drink2 = MixueFactory.create('lemon');   // 我要柠檬水
drink2.show();                                 // 柠檬水, 4元

⚠️ 面试考点:工厂模式的好处?①调用方不用记住所有产品类;②创建逻辑集中在一处,新增产品只改工厂一处;③"创建"和"使用"解耦。面试官最爱考这个。

❌/✅ 对比:有工厂 vs 没工厂

维度 没工厂(直接 new) 有工厂(MixueFactory)
创建逻辑在哪 散落在每个调用方 集中在工厂的 switch
调用方要记什么 每个类名 + 构造参数 只记工厂 + 一个字符串 key
新增一个产品 每个调用方都要改 只改工厂,调用方不动
产品类改名 所有 new 的地方全炸 只改工厂内部
javascript 复制代码
// ❌ 新增"圣代",所有调用点都要加 if ------ 改到怀疑人生
if (type === 'sundae') { new Sundae().show() }

// ✅ 只改工厂一处,调用方一行代码都不用动
case 'sundae':
  return new Sundae()

💡 一句话记住:工厂模式 = 把"new 谁"的决定权,从调用方手里收回来,交给一个专门的工厂类。


五、NestFactory 就是工厂模式

现在回到 NestJS。打开 src/main.ts

typescript 复制代码
// nestjs 按需加载 大型框架的性能优化、模块化的思考
import { NestFactory } from '@nestjs/core';
import { AppModule } from './app.module';

async function bootstrap() {
  // 实例化一个 后端nestjs 应用
  // 面向对象思想
  // 工厂模式
  // nest 可以开发的后端服务太多了,
  // / 首页 由 AppModule 来服务
  const app = await NestFactory.create(AppModule);
  // 启动web http 服务 3000
  await app.listen(process.env.PORT ?? 3000);
}
bootstrap();

看到没?NestFactory 就是一个工厂! 它跟 MixueFactory 干的是同一件事:

MixueFactory NestFactory
你要什么 'ice' → 冰激凌 AppModule → 一个 Nest 应用
工厂给你 IceCream 实例 Nest 应用实例(app
你怎么用 drink.show() app.listen(3000)
你要懂内部细节吗 不用 不用

NestFactory.create(AppModule) 的厉害之处在于:你完全不知道 Nest 内部是怎么组装模块、怎么配中间件、怎么注入依赖的,你只要说"我要一个基于 AppModule 的应用",它就给你造好了一个能跑的后端服务。

这跟你去蜜雪冰城一模一样------你不需要知道奶茶机怎么运转、奶盖怎么打发,你说"要一杯奶茶",店员就给你端上来。


六、高度模块化:App 拆成 Modules

模块化思想:一个文件几千行代码的救星

后端代码最恐怖的事就是一个文件几千行。老师灵魂拷问:你会把一个 3000 行的文件丢给同事维护吗?当然不会。所以 NestJS 的规矩是:

kotlin 复制代码
App(根模块)
  └── Modules(模块)
        ├── @Module 装饰器(来自 @nestjs/common)
        ├── imports:依赖的外界模块
        ├── controllers:控制器(接请求、做校验、简单逻辑,最后 return response)
        └── providers:服务(复杂业务、CRUD、SQL,return 数据)

打开 src/app.module.ts

typescript 复制代码
import { Module } from '@nestjs/common';
// 控制器 检测前端用户输入,一些控制逻辑
import { AppController } from './app.controller';
// 数据库业务, 一些复杂业务 CRUD service 层
import { AppService } from './app.service';

// 复杂, 说明书 照着做
// 装饰器模式
// 快速的给类添加一些行为或方法,
// ts 支持
@Module({
  imports: [],                 // 依赖外界?没有就先空着
  controllers: [AppController], // 控制器 校验,简单逻辑
  providers: [AppService],      // data service 复杂业务
})
export class AppModule {}

@Module 就像一份公司章程:宣告"我这个模块有哪些前台(controllers)、哪些技术工(providers)、依赖谁(imports)"。

MVC 模式:后端最常见的组织方式

说到 Controller 和 Service,就不得不提 MVC。后端最常见的套路就是它:

css 复制代码
M ------ Model      数据库抽象(表 → 对象)
C ------ Controller 控制器(接请求、校验、调度)
V ------ View       视图层(HTML / JSON)

NestJS 里 Controller 和 Service 的分工,是约定

职责 一句话
Controller 接收请求、参数校验、简单逻辑 前台接待:问你办什么业务
Service 复杂业务、CRUD、操作数据库 技术工:真的去干活

💡 一句话记住:Controller 负责"接客",Service 负责"干活"。


七、装饰器模式:@ 符号的魔法

装饰器是什么:不修改原对象,动态加功能

你注意到上面代码里到处是 @ 符号了吗?@Module@Controller@Get@Injectable......这就是装饰器模式

装饰器模式:在不修改原有对象的前提下,动态给对象叠加额外功能。

打个比方:你买了一台普通手机(原始对象),然后给它贴了膜、套了壳、装了手机支架------手机本体没动,但功能变多了。这就是装饰器。

javascript 复制代码
// 没装饰之前:一个普普通通的类
class 手机 {
  call() { /* 打电话 */ }
}

// 装饰器:给它贴个膜(不修改手机本体,动态叠加功能)
@贴膜
class 手机 {
  call() { /* 打电话 */ }
}

在 NestJS 里,装饰器干的活就是"给类贴标签 + 声明行为":

typescript 复制代码
import { Controller, Get } from '@nestjs/common';
import { AppService } from './app.service';

// @Controller() 告诉 Nest:这个类是个"控制器",请注册它、把请求路由给它
@Controller()
export class AppController {
  constructor(private readonly appService: AppService) {}

  // @Get() 告诉 Nest:GET 请求访问根路径时,调用下面这个方法
  @Get()
  getHello(): string {
    console.log('/ 的控制器');
    // 响应什么内容?交给service 层
    // this -> Module
    return this.appService.getHello();
  }
}

再看 Service:

typescript 复制代码
import { Injectable } from '@nestjs/common';

// @Injectable() 告诉 Nest:这个类可以被注入到别的地方使用
@Injectable()
export class AppService {
  // 给controller 层一个交代的
  getHello(): string {
    return 'Hello World!';
  }
}

四个最常用的装饰器,一次记住:

装饰器 大白话 作用
@Module 模块章程 声明一个模块包含哪些控制器和服务
@Controller 我是前台 标记控制器,注册路由
@Get / @Post 我是这个路径的处理者 绑定 HTTP 方法到具体函数
@Injectable 我可以被注入 允许这个服务被 Nest 依赖注入

⚠️ 面试考点:装饰器模式 vs 继承?继承是"爸爸传给孩子"(耦合、影响面大),装饰器是"给对象贴贴纸"(不改原对象、可叠加、可组合)。NestJS 选的是装饰器。


八、一次请求完整走一遍

把前面所有东西串起来。浏览器访问 http://localhost:3000/,发生了啥?

kotlin 复制代码
浏览器 GET / 
    │
    ▼
路由匹配 ──► @Controller() + @Get() 命中 AppController.getHello
    │
    ▼
Controller(前台):校验参数、记录日志,然后喊:技术工,干活!
    │  调 this.appService.getHello()
    ▼
Service(技术工):真正干活,return 'Hello World!'
    │
    ▼
Controller 拿到结果,return response
    │
    ▼
浏览器显示:Hello World!

用代码视角再走一遍:

typescript 复制代码
// ① 入口:工厂造出应用
const app = await NestFactory.create(AppModule);  // 工厂模式
await app.listen(3000);

// ② 根模块:声明前台和技术工
@Module({
  controllers: [AppController],  // 前台
  providers: [AppService],       // 技术工
})
export class AppModule {}

// ③ 前台接客,转给技术工
@Get()
getHello(): string {
  return this.appService.getHello();  // 自己不干活,交给 service
}

// ④ 技术工返工
getHello(): string {
  return 'Hello World!';  // 数据
}

💡 一句话记住:请求来了 → Controller 接 → Service 干 → Controller 回。 前、后端、数据库,各司其职。


九、真实踩坑 2 个

这一节是我写这篇文章时真实踩过的坑,都是新手高频问题。

坑 1:package.json 被覆盖,pnpm devMODULE_NOT_FOUND

我一开始在工厂模式 demo 目录建了个 package.json,里面有 "dev": "node 1.mjs"。结果因为复制/误操作,这个文件内容串到了 hello 项目里 。然后我在 hello 里跑 pnpm dev

bash 复制代码
$ node 1.mjs
Error: Cannot find module '.../hello/1.mjs'

hello 目录里根本没有 1.mjs,可 dev 脚本却指向它。

  • ❌ 原因:package.json 是"启动脚本 + 依赖清单",不同项目各有各的,不能混用 。hello 项目的 package.json 里应该有 nest start 而不是 node 1.mjs
  • ✅ 解决:把 hello 的 package.json 恢复成标准 NestJS 脚手架的内容(start / start:dev 脚本)。

📌 经验:改完 package.jsoncat 看一眼内容,再跑启动命令,别直接 npm run

坑 2:pnpm 11 拦截依赖的构建脚本 ERR_PNPM_IGNORED_BUILDS

hello 项目修好 package.json 后,跑 pnpm run start 又报错:

vbnet 复制代码
[ERR_PNPM_IGNORED_BUILDS] Ignored build scripts: unrs-resolver@1.12.2
Run "pnpm approve-builds" to pick which dependencies should be allowed to run scripts.

这是 pnpm 11 的安全机制 :默认不执行任何依赖的 postinstall 构建脚本 (防止供应链攻击)。但 unrs-resolver(eslint 工具链的原生模块)必须要构建才能用,于是被拦下了。

  • ❌ 误区:往 package.json"pnpm": { "onlyBuiltDependencies": [...] } ------ pnpm 11 已经不读这个字段 了,会警告 The "pnpm" field in package.json is no longer read by pnpm
  • ✅ 正确:配置放在 pnpm-workspace.yaml
yaml 复制代码
# pnpm-workspace.yaml
allowBuilds:
  unrs-resolver: true

改完重新 pnpm install,构建脚本就能跑了:

bash 复制代码
.../node_modules/unrs-resolver postinstall$ node postinstall.js
.../node_modules/unrs-resolver postinstall: Done

📌 经验:pnpm 11 的构建权限在 pnpm-workspace.yamlallowBuilds 里,不在 package.json。


十、面试高频 4 问

① 工厂模式的核心是什么?对比普通代码的好处?

核心:把"创建对象"的逻辑从"使用对象"的代码里抽出来,集中到工厂类。调用方只管说"我要什么",不用管"怎么造、造哪个类"。好处:①调用方不用记住所有产品类;②创建逻辑集中,新增产品只改工厂一处;③创建和使用解耦,产品类改了调用方不受影响。
② NestFactory.create(AppModule) 是什么设计模式?

工厂模式。Nest 内部帮我们组装好了模块、依赖注入、中间件等一堆复杂细节,开发者只要说"我要一个基于 AppModule 的应用",它就返回一个能跑的后端应用。这和 MixueFactory.create('ice') 是一个道理。
③ @Module 里的 controllers 和 providers 有什么区别?

controllers 是控制器(接收请求、参数校验、简单逻辑,最后 return response);providers 是服务(复杂业务、CRUD、操作数据库,return 数据)。Controller 是"前台",Service 是"技术工"。
④ 装饰器模式和继承有什么区别?

继承是"爸爸传给孩子",子类必须继承父类的全部,耦合强、影响面大。装饰器是"给对象贴标签",不修改原对象、可动态叠加、可自由组合,更灵活。NestJS 用装饰器来声明类的能力(@Controller@Injectable 等)。


总结

核心概念速查表

概念 一句话
NestJS Node 的纯后端企业级框架,默认 TypeScript,全面模块化
工厂模式 把"new 谁"的决定权收回到工厂,调用方只说"要什么"
NestFactory Nest 的工厂,create(AppModule) 造出整个应用
模块化 Controller 管接客,Service 管干活,各司其职
装饰器模式 不修改原对象,动态叠加功能(@Controller @Get @Injectable
MVC Model 数据、Controller 控制、View 视图

一句口诀

工厂管创建,模块管分工,装饰器管能力。NestJS 把这三招全用了。

核心代码骨架

typescript 复制代码
// 入口:工厂造应用
const app = await NestFactory.create(AppModule);
await app.listen(3000);

// 根模块:声明前台 + 技术工
@Module({ controllers: [AppController], providers: [AppService] })
export class AppModule {}

// 前台接客,转给技术工
@Get() getHello() { return this.appService.getHello(); }

// 技术工返工
@Injectable() class AppService { getHello() { return 'Hello World!'; } }

结尾

  • 本文 Demo 源码:factory_demo/1.mjs(蜜雪冰城工厂模式)+ hello 项目(NestJS 应用),都在 hjf-ai/backend/nestjs/ 目录下
  • 想自己跑:npm i -g @nestjs/clinest new hellonest start --watch → 浏览器打开 http://localhost:3000

希望这篇文章对你有帮助!有问题欢迎在评论区交流。如果觉得有收获,点赞收藏一下 🔥 我会继续更新 NestJS 系列(依赖注入、模块划分、连接数据库......)

相关推荐
码匠许师傅2 小时前
【C++ 面试真题】聊聊 C++ 的多继承与虚继承
开发语言·c++·面试
kyriewen2 小时前
面试官说"打开你的AI工具"——我才发现,他考的根本不是写代码
前端·人工智能·面试
众人皆醒我独醉7 小时前
AI 工作负载可观测性:DCGM + Prometheus + Grafana
面试·llm·gpu
西门老铁7 小时前
JWT 是什么?三段式结构、签名原理与 6 个安全坑一次讲透
后端·面试
gyx_这个杀手不太冷静7 小时前
高级前端开发职业规划(2026—2035)
前端·面试·agent
一只叫煤球的猫8 小时前
开个新坑,从头开始完整拆解 Spring AI 2.0 的源码
后端·面试·aigc
浅念-11 小时前
MySQL 索引底层完整详解|磁盘Page|B+树推导|聚簇非聚簇索引|索引SQL操作
大数据·数据库·b树·sql·mysql·面试·职场和发展
众人皆醒我独醉11 小时前
GPU 集群 IaC:Terraform + Ansible 部署自动化
面试·ansible·gpu
Dr.kangder11 小时前
嵌入式面试总结(二十二)——指针
java·面试·职场和发展·架构·嵌入式