这是《Nest 第一步》系列的第三篇。
前两篇我们完成了项目搭建和数据库连接。这一篇,我们来理解 NestJS 最核心的设计------三层架构。
用前端开发来理解:
- Controller 就像路由 (相当于前端的
router.js),定义"哪个 URL 对应哪个处理函数"- Service 就像工具函数 (相当于前端的
utils/),封装具体的业务逻辑- Module 就像文件组织 (相当于前端的
index.js汇总导出),把相关代码放在一起搞懂这三层,你就能读懂 NestJS 项目的代码结构,知道"代码应该往哪写"。
一、三层架构图解
我们先用一张图,看清楚三层架构的数据流向:
这张图告诉我们什么?
| 步骤 | 谁在做 | 做什么 |
|---|---|---|
| 1 | 前端 | 发起 HTTP 请求 GET /users |
| 2 | Controller | 接收请求,调用 Service |
| 3 | Service | 处理业务逻辑,调用 Prisma 查数据库 |
| 4 | Prisma | 执行 SQL,从数据库取数据 |
| 5 | Service | 把数据返回给 Controller |
| 6 | Controller | 把数据封装成 JSON 返回给前端 |
| 7 | 前端 | 收到 JSON,渲染页面 |
二、Controller 层:路由和请求处理
前端类比:Controller 就像前端的
router.js,定义"哪个 URL 对应哪个处理函数"
2.1 什么是 Controller?
在 NestJS 中,Controller(控制器) 的职责是:
- 接收 HTTP 请求(GET、POST、PATCH、DELETE 等)
- 解析请求参数(URL 参数、请求体、查询参数等)
- 调用 Service 处理业务
- 返回响应给客户端
2.2 我们现有的 Controller 代码走读
typescript
// ────────────────────────────────────────────────────────────
// 1. 导入依赖
// ────────────────────────────────────────────────────────────
// Controller: 标记这个类是一个控制器
// Get, Post, Patch, Delete: HTTP 方法装饰器
// Body, Param: 参数装饰器,用于获取请求中的数据
import {
Controller,
Get,
Post,
Patch,
Delete,
Body,
Param,
} from '@nestjs/common';
// 导入 Service(业务逻辑层)
import { AppService, UserService } from './app.service.js';
// ────────────────────────────────────────────────────────────
// 2. 控制器类
// ────────────────────────────────────────────────────────────
// @Controller() 装饰器:告诉 NestJS 这个类是一个控制器
// 如果写上前缀如 @Controller('api'),所有路由都会变成 /api/xxx
@Controller()
export class AppController {
// 构造函数:依赖注入 Service
// 相当于"前台配了一个对讲机,可以直接联系后台"
constructor(
private readonly appService: AppService,
private readonly userService: UserService,
) {}
// ──────────────────────────────────────────────────────────
// 路由:GET /
// ──────────────────────────────────────────────────────────
// @Get() 装饰器:处理 GET 请求
// 不传路径表示根路径:/
// 相当于前端路由:{ path: '/', component: ... }
@Get()
getHello(): string {
// 调用 Service 的方法,返回结果
return this.appService.getHello();
}
// ──────────────────────────────────────────────────────────
// 路由:GET /users
// ──────────────────────────────────────────────────────────
// @Get('users'):处理 GET /users 请求
// 相当于前端路由:{ path: '/users', component: ... }
@Get('users')
async getUsers() {
// 调用 UserService 的 getUserList 方法
// 由 Service 层去数据库查询所有用户
return this.userService.getUserList();
}
// ──────────────────────────────────────────────────────────
// 路由:GET /users/:id
// ──────────────────────────────────────────────────────────
// @Get('users/:id'):处理 GET /users/1 这样的请求
// :id 是动态参数,可以匹配 /users/1、/users/2 等
@Get('users/:id')
async getUser(@Param('id') id: string) {
// @Param('id'):从 URL 中提取 id 参数
// 例如请求 GET /users/1 → id = '1'
// 然后传给 Service,转成数字类型
return this.userService.getUserDetail(Number(id));
}
// ──────────────────────────────────────────────────────────
// 路由:POST /users
// ──────────────────────────────────────────────────────────
// @Post('users'):处理 POST /users 请求
// 用于创建新用户
@Post('users')
async createUser(@Body() body: { email: string; name: string }) {
// @Body():从请求体中提取整个 body 对象
// 前端 POST /users 时,在请求体里传 JSON
// 直接传给 Service 处理
return this.userService.createUser(body);
}
// ──────────────────────────────────────────────────────────
// 路由:PATCH /users/:id
// ──────────────────────────────────────────────────────────
// @Patch('users/:id'):处理 PATCH /users/1 这样的请求
// 用于部分更新用户信息
@Patch('users/:id')
async updateUser(
@Param('id') id: string,
@Body() body: { email?: string; name?: string },
) {
// 从 URL 取 id,从 body 取要更新的字段
// 传给 Service 执行更新
return this.userService.updateUser(Number(id), body);
}
// ──────────────────────────────────────────────────────────
// 路由:DELETE /users/:id
// ──────────────────────────────────────────────────────────
@Delete('users/:id')
async deleteUser(@Param('id') id: string) {
return this.userService.deleteUser(Number(id));
}
}
2.3 Controller 常用装饰器总结
| 装饰器 | 用途 | 示例 |
|---|---|---|
@Controller() |
标记类为控制器 | @Controller('api') → 所有路由加 /api 前缀 |
@Get() |
处理 GET 请求 | @Get('list') → GET /list |
@Post() |
处理 POST 请求 | @Post() → POST / |
@Patch() |
处理 PATCH 请求 | @Patch(':id') → PATCH /:id |
@Delete() |
处理 DELETE 请求 | @Delete(':id') → DELETE /:id |
@Param() |
获取 URL 路径参数 | @Param('id') id → 从 /users/1 取出 1 |
@Query() |
获取 URL 查询参数 | @Query('page') page → 从 ?page=2 取出 2 |
@Body() |
获取请求体 | @Body() body → 获取整个 JSON body |
2.4 前端对照表
| 前端概念 | NestJS 对应 |
|---|---|
router.js 定义路由 |
@Get()、@Post() 装饰器 |
useParams() 获取 URL 参数 |
@Param() 装饰器 |
useSearchParams() 获取查询参数 |
@Query() 装饰器 |
fetch() 的 body |
@Body() 装饰器 |
三、Service 层:业务逻辑
前端类比:Service 就像前端的 utils/ 或 api/ 文件夹,封装具体的业务逻辑
3.1 什么是 Service?
Service(服务) 的职责是:
- 处理具体的业务逻辑
- 操作数据库(通过 Prisma)
- 调用外部 API
- 处理数据加工
简单说:Service 是"后台"------Controller 接到订单(请求)后,告诉 Service "去做事情",Service 做好后把(数据)返回来。
3.2 为什么要有 Service 层?
| 如果不分 Service | 如果分了 Service |
|---|---|
| 所有代码都写在 Controller 里 | Controller 只负责路由和参数解析 |
| 同一个业务逻辑被多个 Controller 重复写 | 一个 Service 被多个 Controller 复用 |
| 改业务逻辑要改 Controller,改路由也要改 Controller | 改业务逻辑只改 Service,Controller 不动 |
核心原则:Controller 只管"接"和"返",Service 只管"做"。
3.3 我们现有的 Service 代码走读
typescript
// ────────────────────────────────────────────────────────────
// 1. 导入依赖
// ────────────────────────────────────────────────────────────
// Injectable: 标记类可以被依赖注入
import { Injectable } from '@nestjs/common';
// PrismaService: 数据库操作服务
import { PrismaService } from './prisma.service.js';
// UserModel: Prisma 生成的类型,用于类型检查
import type { UserModel } from './generated/prisma/models.js';
// ────────────────────────────────────────────────────────────
// 2. AppService:应用基础服务
// ────────────────────────────────────────────────────────────
@Injectable()
export class AppService {
// 这是一个同步方法,直接返回字符串
getHello(): string {
return 'Hello World!';
}
}
// ────────────────────────────────────────────────────────────
// 3. UserService:用户业务服务
// ────────────────────────────────────────────────────────────
@Injectable()
export class UserService {
// 构造函数:注入 PrismaService
// 相当于"后厨配了一个冰箱(数据库),可以直接取食材"
constructor(private readonly prisma: PrismaService) {}
// 下方这些方法被 Controller 调用
// 方法负责"去数据库" 把用户查出来或编辑、删除用户
// ──────────────────────────────────────────────────────────
// 业务方法:查询所有用户
// ──────────────────────────────────────────────────────────
async getUserList(): Promise<UserModel[]> {
return this.prisma.user.findMany();
}
// ──────────────────────────────────────────────────────────
// 业务方法:根据 ID 查询单个用户
// ──────────────────────────────────────────────────────────
async getUserDetail(id: number): Promise<UserModel | null> {
return this.prisma.user.findUnique({
where: { id },
});
}
// ──────────────────────────────────────────────────────────
// 业务方法:创建用户
// ──────────────────────────────────────────────────────────
async createUser(data: { email: string; name: string }): Promise<UserModel> {
return this.prisma.user.create({
data,
});
}
// ──────────────────────────────────────────────────────────
// 业务方法:更新用户
// ──────────────────────────────────────────────────────────
async updateUser(
id: number,
data: { email?: string; name?: string },
): Promise<UserModel> {
return this.prisma.user.update({
where: { id },
data,
});
}
// ──────────────────────────────────────────────────────────
// 业务方法:删除用户
// ──────────────────────────────────────────────────────────
async deleteUser(id: number): Promise<UserModel> {
return this.prisma.user.delete({
where: { id },
});
}
}
3.4 Prisma 常用方法速查表
在 Service 里操作数据库时,最常用的是 Prisma 提供的这 5 个方法:
| Prisma 方法 | 用途 | 对应 SQL |
|---|---|---|
findMany() |
查询多条记录 | SELECT * FROM "User" |
findUnique() |
根据唯一字段查询单条记录 | SELECT * FROM "User" WHERE id = ? |
create() |
创建一条记录 | INSERT INTO "User" ... |
update() |
更新一条记录 | UPDATE "User" SET ... WHERE id = ? |
delete() |
删除一条记录 | DELETE FROM "User" WHERE id = ? |
其他常用方法:
| Prisma 方法 | 用途 |
|---|---|
findFirst() |
查询符合条件的第一条记录 |
count() |
统计记录数量 |
upsert() |
存在则更新,不存在则创建 |
deleteMany() |
删除多条记录 |
updateMany() |
更新多条记录 |
3.5 前端对照表
| 前端概念 | NestJS 对应 |
|---|---|
utils/ 工具函数 |
Service 里的方法 |
api/ 接口封装 |
Service 里调用 Prisma |
| 状态管理里的 actions | Service 里的业务方法 |
四、Module 层:组织代码
前端类比:Module 就像前端的
index.js汇总导出,把相关代码组织在一起
4.1 什么是 Module?
Module(模块) 的职责是:
- 把 Controller 和 Service 组织在一起
- 注册所有可被依赖注入的类
- 导入其他模块的功能
简单说:Module 是"公司组织架构图"------它告诉 NestJS:这个模块里有哪些前台(Controller)、哪些后台(Service)。
4.2 我们现有的 Module 代码走读
typescript
// ────────────────────────────────────────────────────────────
// 1. 导入依赖
// ────────────────────────────────────────────────────────────
// Module: 标记类为模块
import { Module } from '@nestjs/common';
// 控制器:前台负责处理 HTTP 请求
import { AppController } from './app.controller.js';
// 服务:后台负责业务逻辑
import { AppService, UserService } from './app.service.js';
// 数据库服务
import { PrismaService } from './prisma.service.js';
// ────────────────────────────────────────────────────────────
// 2. 根模块 AppModule
// ────────────────────────────────────────────────────────────
@Module({
// imports: 导入其他模块(暂无)
// 相当于"引用外部公司的部门"
imports: [],
// controllers: 注册控制器(前台)
// 告诉 NestJS 这个模块有哪些前台接待
controllers: [AppController],
// providers: 注册服务(后台)
// 告诉 NestJS 这个模块有哪些后台
// 所有在 providers 中注册的类,都可以被"依赖注入"到其他类中
providers: [
AppService, // 基础服务
UserService, // 用户业务服务
PrismaService, // 数据库服务
],
})
export class AppModule {}
4.3 Module 的三个核心属性
| 属性 | 作用 | 类比 |
|---|---|---|
imports |
导入其他模块的功能 | "借用其他部门的资源" |
controllers |
注册控制器(路由) | "前台接待名单" |
providers |
注册服务(可被注入) | "后台员工名单" |
动手实践:按邮箱查询用户
5.1 在 Service 中添加方法
在 src/app.service.ts 的 UserService 中添加:
csharp
// ──────────────────────────────────────────────────────────
// 业务方法:根据邮箱查询单个用户
// ──────────────────────────────────────────────────────────
// 对应路由:GET /users/email/:email
// 例如:GET /users/email/user1@zhang.com
async getUserByEmail(email: string): Promise<UserModel | null> {
// Prisma 执行 SQL:SELECT * FROM "User" WHERE email = ?;
return this.prisma.user.findUnique({
where: { email },
});
}
5.2 在 Controller 中添加路由
在 src/app.controller.ts 的 AppController 中添加:
less
// ──────────────────────────────────────────────────────────
// 路由:GET /users/email/:email
// ──────────────────────────────────────────────────────────
@Get('users/email/:email')
async getUserByEmail(@Param('email') email: string) {
return this.userService.getUserByEmail(email);
}
5.3 测试
浏览器访问 http://localhost:3000/users/email/user1@zhang.com

六、总结
三层职责一句话概括
| 层级 | 一句话概括 | 前端类比 |
|---|---|---|
| Controller | 定义 URL 和接收请求 | 路由配置 |
| Service | 写具体的业务逻辑 | 工具函数 / API 封装 |
| Module | 把 Controller 和 Service 组装起来 | 模块导出汇总 |
代码组织原则
Controller 收到请求 → 交给 Service 处理 → 返回结果给客户端
记住:
- Controller 里不要写业务逻辑(只做路由和参数处理)
- Service 里不要写路由(只做业务逻辑)
- Module 里注册所有需要用的东西
欢迎关注,一起从 React/Vue 走向全栈。