NestJS 学习分享:从工厂模式到企业级后端开发
NestJS 是一个基于 Node 的纯后端企业级开发框架 。它默认使用 TypeScript,奉行全面模块化的思想,非常适合用来构建企业级服务。下面结合我们的示例项目,把其中的核心概念梳理一遍。
一、后端开发到底在做些什么?
在动手写代码之前,先搞清楚后端开发的定位。后端开发主要做几类事情:
- 提供 API 接口:做 Web 开发,给前端提供数据服务
- 系统集成:处理并发、底层服务、AI Infra 等
- 微服务:构建可拆分的服务架构
这些场景都需要一个稳定、可扩展的框架,而 NestJS 正是为此而生。
二、安装与目录结构
bash
npm i -g @nestjs/cli # 全局安装脚手架
nest new hello # 创建项目
nest run start # 启动项目
生成后的目录结构核心在 src 下:
markdown
- src
- main.ts # 入口文件
- app.module.ts # 根模块
其中:
main.ts是程序入口,负责启动应用app.module.ts是根模块,负责把各个业务模块组织起来
三、工厂模式(Factory Pattern)
3.1 设计模式的本质
设计模式是面向接口的编程 ,是抽象的。经典的设计模式一共有 23 种,工厂模式就是其中之一。
3.2 一个通俗的比喻
工厂模式可以这样理解:
你想喝奶茶,不用自己做(不要写流程代码 NO),直接找蜜雪冰城。 工厂?------柠檬水、圣代、芋圆......
在 NestJS 里,NestFactory 就相当于「蜜雪冰城」这个工厂,它满足我们创建 App 的各种需要。通过实现一个 MixueFactory,帮助我们提供工厂里的各种「类」,并且把这些五花八门的类解耦。
开发者只需要一行:
lua
MixueFactory.create(type);
就能拿到想要的产品。由于工厂里的每个类都实现了相同的 show 接口,所以由工厂生产出来的类,可以直接放心调用。
3.3 一个具体例子
下面是工厂模式的完整演示(factory_demo/1.mjs):
typescript
// 蜜雪冰城产品之一 冰激凌
class IceCream {
constructor() {
this.name = '冰激凌';
this.price = 3;
}
show() {
console.log(`${this.name} 价格${this.price}`)
}
}
class LeamonTea {
constructor() {
this.name = '柠檬水';
this.price = 5;
}
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 'leamon': return new LeamonTea();
case 'milk': return new MilkTea();
default: return null;
}
}
}
// 管理并返回冰激凌这个类
const drink1 = MixueFactory.create('ice');
drink1.show();
const drink2 = MixueFactory.create('leamon');
drink2.show();
const drink3 = MixueFactory.create('milk');
drink3.show();
这个例子的核心思想是:一个企业有很多产品,每一种产品都实现了相同的接口(方法) 。开发者不用记住工厂里那么多实现细节,只需要直接和工厂类打交道即可。
四、高度模块化与 MVC 架构
NestJS 采用高度模块化的约定:
kotlin
App
└── Modules
├── @nestjs/common 的 Module 类
├── import 依赖项
├── controller 控制器 ------ 参数校验、简单逻辑,最后 return response
└── service 服务 ------ return 数据
4.1 MVC 的本质
main.ts 中的注释点明了 NestJS 的本质:
csharp
// M Model 数据可抽象
// C Controller 控制器
// V View 视图
// 面向对象思想
// 工厂模式
const app = await NestFactory.create(AppModule);
await app.listen(process.env.PORT ?? 3000);
- Model:数据可抽象
- Controller:控制器,负责检测前端用户输入、做一些控制逻辑
- View :视图层,不可以直接去数据库查数据
其中 NestFactory.create(AppModule) 就是工厂模式的体现------localhost:3000/ 的后端路由会被送到 AppModule,由它组织 controller 和 service 层,完成 CRUD 和 SQL 操作。
4.2 根模块 AppModule
less
@Module({
imports: [TodosModule], // 依赖外界(导入其他模块)
controllers: [AppController], // 控制器:校验、简单逻辑
providers: [AppService], // data service:复杂业务
})
export class AppModule {}
根模块像一份「说明书」,照着做即可。它把三样东西组装在一起:
imports:引入依赖的外部模块controllers:控制器列表providers:服务(数据层)列表
4.3 开发流程总结
-
在
AppModule的import里植入我们自己的 Module -
Module 是 NestJS 的独立业务模块,由三个文件定义:
xx.module.ts定义、组装模块xx.controller.ts定义控制器xx.service.ts定义服务
-
用
@Injectable()实现自动依赖注入,自动注入到 controller 或任何使用它的地方 -
controller 里的一个属性就能拿到 service 实例
五、装饰器模式(Decorator Pattern)
装饰器模式在不修改原有对象的前提下,动态地给对象叠加额外功能 。在 TypeScript 中,用 @ 加在 class 前面即可实现。
NestJS 把这个模式用到了极致,常见的装饰器有:
@Module():声明一个模块@Controller('todos'):声明一个控制器,并指定路由前缀@Injectable():把一个普通类变成 Nest 容器可管理的服务@Get()/@Post()/@Put()/@Delete():声明 HTTP 请求方法@Param('id')/@Body():提取请求参数和请求体
这些装饰器「快速地给类添加一些行为或方法」,正是 TypeScript 对装饰器的支持让这一切成为可能。
六、控制器与服务:各司其职
6.1 Service 层(数据与业务)
kotlin
@Injectable()
export class AppService {
// 给 controller 层一个交代
getHello(): string {
return 'hello world!';
}
}
Service 层负责数据库业务、复杂业务 CRUD ,最后 return 数据 给 controller。
6.2 Controller 层(校验与调度)
less
@Controller()
export class AppController {
constructor(private readonly appService: AppService) {}
@Get()
getHello(): string {
console.log('/ 的控制器');
// 响应什么内容?交给 service 层
// this -> Module
return this.appService.getHello();
}
}
Controller 层负责参数校验、简单逻辑,把真正的数据响应交给 service 层。
这里有一个关键点:controller 通过 constructor(private readonly appService: AppService) 拿到 service 实例,这是依赖注入 的结果,而不是 import 之后手动 new 实例化。
七、完整 CRUD 示例:Todos 模块
7.1 模块定义 todos.module.ts
kotlin
@Module({
controllers: [TodosController],
providers: [TodosService],
})
export class TodosModule {}
这个文件负责「组装」,把控制器和服务绑定到这个业务模块里。
7.2 数据与类型 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: '学习 ', completed: false },
]
let nextId = 3;
@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} 不存在`);
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} 不存在`);
todos.splice(index, 1);
}
update(id: number, patch: Partial<Todo>): Todo {
const todo = this.findOne(id);
Object.assign(todo, patch);
return todo;
}
}
7.3 控制器 Todos.controller.ts
less
@Controller('todos')
export class TodosController {
constructor(private readonly todosService: TodosService) {}
@Get()
findAll(): Todo[] {
console.log('/todos controller');
return this.todosService.findAll();
}
@Get(':id')
findOne(@Param('id') id: string): Todo {
console.log(id);
return this.todosService.findOne(Number(id));
}
@Post()
create(@Body('title') title: string): Todo {
return this.todosService.create(title);
}
@Delete(':id')
remove(@Param('id') id: string): {message: string} {
this.todosService.remove(Number(id));
return {message: '删除成功'};
}
// Patch
@Put(':id')
update(
@Param('id') id: string,
@Body() patch: Partial<Todo>,
): Todo {
return this.todosService.update(Number(id), patch);
}
}
通过这个例子可以看到,一个业务模块完整地串起了 Module → Controller → Service 三层:路由到 controller,controller 校验参数后调用 service,service 处理数据后 return 回去。
八、错误处理:NotFoundException
后端业务讲究严谨稳定 ,需要一个「容错模块」。NestJS 提供了各种内置的错误类,用来标准化错误输出,例如:
javascript
if(!todo) throw new NotFoundException(`Todo ${id} 不存在`);
NotFoundException 是 NestJS 内置的错误类。它让报错输出统一包含:
statusCode:状态码message:消息
相比在 TypeScript 里到处写 try catch finally(线程挂掉的风险),NestJS 提供的错误类让错误处理更规范、更统一。
九、测试:控制器单元测试
NestJS 自带测试能力,示例项目里的 app.controller.spec.ts 演示了如何测试一个控制器:
ini
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/testing 的 Test.createTestingModule 可以创建一个独立的测试模块,注入需要的 controller 和 provider,然后验证返回值。
总结:重要知识回顾
| 知识点 | 核心内容 |
|---|---|
| NestJS 定位 | 基于 Node 的纯后端企业级框架,默认 TypeScript,全面模块化 |
| 后端开发内容 | 提供 API 接口、系统集成(并发/底层/AI Infra)、微服务 |
| 工厂模式 | 面向接口编程,NestFactory.create(AppModule) 即工厂创建应用;产品类实现相同接口,调用方只需 Factory.create(type) |
| MVC 架构 | Model(数据抽象)、Controller(校验/简单逻辑)、View(不直接查库);main.ts 启动,路由交给 AppModule 组织 |
| 模块化 | @Module() 用 imports / controllers / providers 组装模块,AppModule 是根模块 |
| 三层文件约定 | xx.module.ts 组装、xx.controller.ts 控制器、xx.service.ts 服务 |
| 装饰器模式 | 不改原对象、动态叠加功能;@ + class,@Module / @Controller / @Injectable / @Get / @Post / @Put / @Delete / @Param / @Body |
| 依赖注入 | @Injectable() 把普通类变成容器可管理服务,constructor 注入,无需手动 new |
| 分层职责 | Service 处理复杂业务 CRUD 并 return 数据;Controller 做校验/简单逻辑并 return response |
| 错误处理 | NotFoundException 等内置错误类,标准化输出 statusCode + message |
| 测试 | @nestjs/testing 的 Test.createTestingModule 构建测试模块并验证返回值 |
一句话记住这套体系 :NestJS 用工厂模式 创建应用、用装饰器模式 给类加行为、用模块化 + MVC 组织代码,Controller 管输入校验、Service 管数据和业务、@Injectable 负责依赖注入、内置错误类负责标准化容错------最终形成一套稳定、可扩展的企业级后端开发方案。