🎃 前端学了 Next.js,后端该学啥?NestJS 就是 Node 版的蜜雪冰城

写在前面:前几节课还在用 Next.js 搭博客搞全栈,今天画风一转------NestJS 。老师说 Next.js 是全栈框架,NestJS 则是 Node 的纯后端企业级开发框架,默认 TypeScript,全面模块化。今天从 nest new 开始,用工厂模式启动应用、用模块拆分业务、用装饰器给类贴标签、用依赖注入让 Service 自动到岗,最后手搓了一个完整的 Todos CRUD。最妙的是,课堂用蜜雪冰城来类比工厂模式------NestFactory 就是 MixueFactory。以下所有代码均来自课堂真实文件。


一、NestJS 是什么:Next.js 的"后端表哥"

一句话区分:

框架 定位 擅长
Next.js 全栈框架 前端 SSR/SSG + API 路由
NestJS 纯后端框架 企业级 API、微服务、AI Infra

readme 开篇就定了调子:

"Next.js 全栈,NestJS 就是 Node 的纯后端企业级开发框架。默认使用 TypeScript,全面模块化思想,适合构建企业级服务。"

后端开发到底做什么?readme 列了三个方向:

  • 提供 API 接口(Web 开发)
  • 系统集成(并发、底层服务、AI Infra)
  • 微服务

安装也极其简单:

bash 复制代码
npm i -g @nestjs/cli
nest new hello

装完 CLI,一条命令创建项目。目录结构精简到令人发指:

ruby 复制代码
src/
├── main.ts          # 入口文件
└── app.module.ts    # 根模块

就两个文件,一个管启动,一个管组织。NestJS 的哲学已经藏在目录结构里了------入口归入口,业务归业务


二、工厂模式:想喝奶茶,不用自己泡

NestJS 的第一个门槛不是路由,不是中间件,而是------工厂模式

readme 用了一个绝妙的比喻:

"你想喝奶茶,不用自己做(流程代码 NO),找蜜雪冰城。"

蜜雪冰城有什么?柠檬水、圣代、芋圆葡萄、冰激凌、珍珠奶茶......

你不需要知道每种饮品怎么做的,你只需要跟柜台说一句"要一杯柠檬水",工厂负责生产。这就是工厂模式的本质------调用者和生产者解耦

课堂的 1.mjs 写了一个完整的蜜雪冰城工厂:

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();
    }
  }
}

const drink1 = MixueFactory.create('ice');
drink1.show();   // 冰激凌 价格3 元
const drink2 = MixueFactory.create('lemon');
drink2.show();   // 柠檬水 价格4 元

注意一个关键设计:每个产品类都实现了相同的 show() 接口。 冰激凌 3 块、柠檬水 4 块、珍珠奶茶 8 块------价格不同,但都叫 show()。这意味着不管工厂返回哪个类,调用者都可以放心地直接调 show(),不用关心具体是哪种产品。

readme 的总结:

"由于工厂里的每个类都实现了相同的 show 接口,由工厂类生产出的类,可以放心的直接调用。"

NestJS 里这个工厂叫 NestFactorymain.ts 入口文件就一行核心代码:

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

async function bootstrap() {
  // 工厂模式:NestFactory 就是蜜雪冰城
  const app = await NestFactory.create(AppModule);
  await app.listen(process.env.PORT ?? 3000);
}
bootstrap();

NestFactory.create(AppModule)------你跟 Nest 工厂说"我要一个应用",工厂就给你造一个。你不需要知道应用内部怎么实例化的,工厂负责生产,你负责消费


三、模块化:每个 Module 就是一个"独立档口"

蜜雪冰城不是一个档口卖所有东西,而是分了冷饮档、冰品档、奶茶档。NestJS 也一样------每个业务一个 Module,互不干扰。

根模块:AppModule

app.module.ts 是总部,负责把所有子模块组装到一起:

typescript 复制代码
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 {}

readme 对 @Module 的注释一针见血:

"复杂,说明书,照着做。"

@Module 就是一份"部门组织架构说明书"------谁管前台(controllers),谁管后厨(providers),需要对接哪些外部部门(imports),写清楚就行。

业务模块:TodosModule

todos.module.ts 是一个独立的业务部门:

typescript 复制代码
import { Module } from '@nestjs/common';
import { TodosController } from './todos.controller';
import { TodosService } from './todos.service';

// 大型后端框架,MVC 视图层 不可以直接去数据库查数据
@Module({
  controllers: [TodosController],
  providers: [TodosService],
})
export class TodosModule {}

注释里有一句关键提醒:

"大型后端框架,MVC 视图层不可以直接去数据库查数据。"

这行注释道出了 NestJS 的铁律------Controller 不碰数据库,Service 不管路由。 各司其职,这就是 MVC 的核心。


四、装饰器模式:给类贴标签的艺术

NestJS 的代码里到处都是 @ 符号------@Module@Controller@Get@Post@Injectable。这些就是装饰器

readme 的解释:

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

通俗点说------你的类是一个员工,装饰器就是工牌上贴的标签。贴上 @Controller,它就是前台接单员;贴上 @Injectable,它就是后厨师傅;贴上 @Get,它就知道这个方法要响应 GET 请求。

NestJS 把装饰器用到了极致。看看 app.controller.ts

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

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

  @Get()
  getHello(): string {
    console.log('/ 的控制器');
    return this.appService.getHello();
  }
}

@Controller() 告诉 NestJS:这个类是控制器。@Get() 告诉 NestJS:这个方法响应 GET 请求。不需要写路由配置文件,不需要注册路由表------标签一贴,路由生效。


五、MVC 分层:前台接单,后厨干活

NestJS 的核心架构就是经典 MVC,但拆得极其干净:

角色 文件 职责 蜜雪冰城类比
Controller xx.controller.ts 接收请求、参数校验、简单逻辑 前台接单员
Service xx.service.ts 业务逻辑、数据操作(CRUD) 后厨师傅
Module xx.module.ts 组装 Controller 和 Service 店长

前台:Controller

Controller 的职责很明确------接单,然后交给后厨。

app.controller.tsgetHello() 方法就干了一件事:调用 Service。

typescript 复制代码
@Get()
getHello(): string {
  console.log('/ 的控制器');
  // 响应什么内容?交给 service 层
  return this.appService.getHello();
}

注释说得很直白:

"响应什么内容?交给 service 层。"

前台不做饭。前台只负责:接单 → 转交后厨 → 把成品端给顾客。

后厨:Service

app.service.ts 更简单:

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

@Injectable()
export class AppService {
  // 给 controller 层一个交代的
  getHello(): string {
    return 'Hello World!';
  }
}

注释里的"给 controller 层一个交代的"------后厨做完菜,给前台一个交代。分工明确,各管一摊。


六、依赖注入:员工自动到岗,不用你 new

传统开发里,Controller 需要用 Service 时得自己 import 然后 new 一个实例。NestJS 不需要。

app.controller.ts 的构造函数:

typescript 复制代码
constructor(private readonly appService: AppService) {}

没有 new,没有 import 后手动实例化。你只要在构造函数里声明"我需要 AppService",NestJS 就自动帮你注入。

魔法在哪?在 @Injectable() 装饰器。

app.service.ts 头上贴了 @Injectable()

typescript 复制代码
@Injectable()
export class AppService { ... }

这个标签告诉 NestJS:这个类可以被自动注入到任何需要它的地方。 Controller 需要它?注入。其他 Service 需要它?注入。不需要手动 new,不需要管生命周期------NestJS 全包了。

readme 的原话:

"@Injectable() 自动依赖注入,自动注入 controller 或任何用它的地方,controller 里的一个属性。"


七、CRUD 实战:Todos 的完整业务闭环

课堂用一个 Todos(待办事项)模块把 CRUD 跑了一遍。这是 NestJS 最经典的入门场景。

数据层:Todo 接口

todos.service.ts 定义了数据结构:

typescript 复制代码
export interface Todo {
  id: number;
  title: string;
  completed: boolean;
}

let todos: Todo[] = [
  { id: 1, title: '学习nestjs', completed: false },
  { id: 2, title: '学习 CRUD', completed: true },
];

两条初始数据,一明一暗:一个还没学完 NestJS,一个已经学完 CRUD。

Controller:五种 HTTP 方法的完整映射

todos.controller.ts 是今天最重磅的文件,把 RESTful API 的五种操作全用上了:

typescript 复制代码
@Controller('todos')
export class TodosController {
  constructor(private readonly todosService: TodosService) {}

  // GET /todos ------ 查全部
  @Get()
  findAll(): Todo[] {
    return this.todosService.findAll();
  }

  // GET /todos/:id ------ 查一条
  @Get(':id')
  findOne(@Param('id') id: string): Todo {
    return this.todosService.findOne(Number(id));
  }

  // POST /todos ------ 新增
  @Post()
  create(@Body('title') title: string): Todo {
    return this.todosService.create(title);
  }

  // DELETE /todos/:id ------ 删除
  @Delete(':id')
  remove(@Param('id') id: string): { message: string } {
    this.todosService.remove(Number(id));
    return { message: `Todo ${id} removed` };
  }

  // PUT /todos/:id ------ 修改
  @Put(':id')
  update(
    @Param('id') id: string,
    @Body() patch: Partial<Todo>,
  ): Todo {
    return this.todosService.update(Number(id), patch);
  }
}

逐个拆解:

装饰器 HTTP 方法 路由 参数提取 操作
@Get() GET /todos 查全部
@Get(':id') GET /todos/1 @Param('id') 查一条
@Post() POST /todos @Body('title') 新增
@Delete(':id') DELETE /todos/1 @Param('id') 删除
@Put(':id') PUT /todos/1 @Param + @Body 修改

注意 @Put 的参数:@Body() patch: Partial<Todo>Partial<Todo> 是 TypeScript 的工具类型------把 Todo 的所有字段变成可选的。你可以只传 { completed: true },也可以传 { title: '新标题', completed: false },灵活得很。

Service:后厨的五大菜谱

todos.service.ts 对应五个方法:

typescript 复制代码
@Injectable()
export class TodosService {
  findAll(): Todo[] {
    return todos;
  }

  findOne(id: number): Todo {
    const todo = todos.find(t => t.id === id);
    if (!todo) throw new NotFoundException(`Todo ${id} not found`);
    return todo;
  }

  create(title: string): Todo {
    const todo: Todo = { id: nextId++, title, completed: false };
    todos.push(todo);
    return todo;
  }

  remove(id: number): void {
    const index = todos.findIndex(t => t.id === id);
    if (index === -1) throw new NotFoundException(`Todo ${id} not found`);
    todos.splice(index, 1);
  }

  update(id: number, patch: Partial<Todo>): Todo {
    const todo = this.findOne(id);
    Object.assign(todo, patch);
    return todo;
  }
}

Controller 负责接单和参数校验,Service 负责干活。Controller 里一行业务逻辑都没有------这就是 readme 说的"controller 参数校验,简单逻辑,最后 return response"。

错误处理:NotFoundException

todos.service.ts 里有两处错误处理,都用的是 NestJS 内置的 NotFoundException

typescript 复制代码
if (!todo) throw new NotFoundException(`Todo ${id} not found`);

readme 特别强调了错误处理的重要性:

"nestjs 提供了各种错误类,标准化错误输出。statusCode 状态码,message 消息。"

不用自己拼 JSON 错误响应,NotFoundException 一抛,NestJS 自动返回标准的 404 响应,带状态码和消息。省心。

对比一下传统方式------try catch finally,TypeScript 独苗,一个线程挂了全崩。NestJS 的内置错误类把异常处理标准化了,不用每个接口都写 try-catch


八、测试:NestJS 的出厂质检

NestJS 脚手架自带测试文件。app.controller.spec.ts

typescript 复制代码
import { Test, TestingModule } from '@nestjs/testing';
import { AppController } from './app.controller';
import { AppService } from './app.service';

describe('AppController', () => {
  let appController: AppController;

  beforeEach(async () => {
    const app: TestingModule = await Test.createTestingModule({
      controllers: [AppController],
      providers: [AppService],
    }).compile();

    appController = app.get<AppController>(AppController);
  });

  describe('root', () => {
    it('should return "Hello World!"', () => {
      expect(appController.getHello()).toBe('Hello World!');
    });
  });
});

NestJS 用 Test.createTestingModule() 创建了一个测试专用的模块------相当于蜜雪冰城开业前的内部试吃。Controller 和 Service 都在测试模块里实例化好,直接调用验证结果。

expect(appController.getHello()).toBe('Hello World!')------简单粗暴,调一下看返回对不对。出厂质检,合格才能上柜。


九、一张图看懂 NestJS 的请求流程

一个 HTTP 请求从到达到返回,在 NestJS 里经历了什么:

scss 复制代码
HTTP 请求
  ↓
NestFactory.create(AppModule)  ← 工厂启动应用
  ↓
AppModule (根模块)              ← 总调度
  ↓
TodosModule (业务模块)          ← 路由匹配
  ↓
TodosController                 ← 前台接单:@Get / @Post / @Delete / @Put
  ↓ (自动依赖注入 TodosService)
TodosService                    ← 后厨干活:CRUD 业务逻辑
  ↓
NotFoundException (找不到时)    ← 标准化错误
  ↓
HTTP 响应 (JSON)

工厂造应用 → 模块分业务 → 控制器接请求 → 服务干逻辑 → 错误类兜底。每一层职责单一,层层递进。


十、设计模式不是玄学

今天最深的感受是------NestJS 把设计模式用成了基础设施。

  • 工厂模式NestFactory.create() 造应用,MixueFactory.create('ice') 造冰激凌。调用者不需要知道内部细节,工厂负责生产。
  • 装饰器模式@Controller@Get@Injectable 给类贴标签,不修改原有代码就叠加功能。
  • 依赖注入@Injectable() 声明可注入,构造函数声明需求,NestJS 自动装配。
  • 模块化@Module 把 Controller 和 Service 打包,业务隔离,按需加载。

readme 里说设计模式是"面向接口的编程,抽象的,23 种"。今天用了两种(工厂 + 装饰器),NestJS 里处处都是它们的影子。

设计模式不是面试时背的八股文,是工程化开发的肌肉记忆。


PS:前端有 Next.js,后端有 NestJS。一个是全栈瑞士军刀,一个是企业级蜜雪冰城。搭配使用,风味更佳。

相关推荐
前端snow1 小时前
ai agent --- 实现 openclaw 定时间效果
前端
码云之上2 小时前
换模型之后,Chatbot 为什么要自己做 compact?
前端·agent·前端工程化
এ慕ོ冬℘゜2 小时前
navigator 导航对象 BOM制作方法
javascript
星栈2 小时前
自定义 UI-UX-Pro-Max 的 CSV 知识库,把 AI 生成的后台拉回行业该有的样子
前端·agent·weui
研☆香2 小时前
简单的图片上传 删除 预览
前端
ITresearchGuest2 小时前
我用 AI 一小时写了一个世界杯数据可视化平台|前端 VibeCoding 初体验
前端·人工智能·信息可视化
慧一居士3 小时前
Vite项目中使用Less步骤,详细使用示例
前端·css
花间相见4 小时前
【LangChain组件03】—— LangChain Agents 执行与状态:工作流程与状态管理实战
java·前端·langchain
kyriewen4 小时前
面试官让我用 AI 重构一个 8 年陈的 React 组件——他说他不看代码,只看我会不会拆
前端·javascript·面试