NestJS从Controller到数据库完整链路实现
NestJS从Controller到数据库完整链路实现
学习目标:打通从 Controller 到数据库的完整链路,掌握分页查询、参数转换、路径别名、全局模块、Prisma 事务处理。
一、完整链路实现流程
1.1 从 Controller 到数据库的完整流程
完整链路流程:
│
├── 第一步:前端发送请求
│ └── GET /home?page=1&size=10
│
├── 第二步:Controller 接收请求
│ ├── 获取 Query 参数(page、size)
│ ├── 使用 Pipe 转换参数类型(string → number)
│ └── 调用 Service 方法
│
├── 第三步:Service 处理业务逻辑
│ ├── 接收 Controller 传递的参数
│ ├── 调用 Prisma 查询数据库
│ └── 返回数据给 Controller
│
├── 第四步:Prisma 操作数据库
│ ├── 使用 findMany() 查询数据
│ ├── 使用 count() 统计总数
│ └── 使用 $transaction() 处理事务
│
├── 第五步:数据库返回数据
│ └── PostgreSQL 返回查询结果
│
├── 第六步:Prisma 映射数据
│ └── 将 SQL 结果映射为对象
│
├── 第七步:Service 返回数据
│ └── 将 [data, total] 返回给 Controller
│
├── 第八步:Controller 封装响应
│ ├── 解构数组为对象
│ └── 返回 { data, total }
│
└── 第九步:前端接收响应
└── { data: [...], total: number }1.2 各层职责回顾
| 层级 | 职责 | 关键操作 |
|---|---|---|
| Controller | 接收请求、参数解析、响应封装 | 获取参数、调用 Service、封装响应 |
| Service | 业务逻辑、数据转换 | 分页计算、调用 Prisma、事务处理 |
| Prisma | ORM 操作、SQL 生成 | findMany、count、$transaction |
| Database | 数据持久化 | 存储、查询 |
二、分页查询实现
2.1 获取分页参数
Controller 层:
// src/modules/home/home.controller.ts
@Controller('home')
export class HomeController {
constructor(private readonly homeService: HomeService) {}
@Get()
async findAll(
@Query('page', ParseIntPipe) page: number = 1,
@Query('size', ParseIntPipe) size: number = 10,
) {
const [data, total] = await this.homeService.findAll(page, size);
return { data, total };
}
}参数说明:
page:页码,默认为 1size:每页条数,默认为 10ParseIntPipe:将 string 转换为 number
请求示例:
GET /home?page=1&size=10
GET /home?page=2&size=202.2 Query 参数的类型问题
问题:在 NestJS 中,通过 @Query() 获取的参数类型永远是 string。
测试代码:
@Get()
async findAll(
@Query('page') page: number, // TypeScript 类型是 number
@Query('size') size: number, // 但实际值是 string
) {
console.log(typeof page); // 输出:'string'
console.log(typeof size); // 输出:'string'
}解决方案:使用 Pipe 进行类型转换。
三、NestJS Pipes(管道)详解
3.1 Pipes 的作用
Pipes(管道)的两大作用:
│
├── 1. 数据转换(Transformation)
│ ├── 将输入数据转换为所需格式
│ ├── 例如:string → number
│ └── 例如:日期字符串 → Date 对象
│
└── 2. 数据校验(Validation)
├── 验证输入数据是否符合要求
├── 例如:是否为空、是否为有效邮箱
└── 校验失败抛出异常3.2 内置 Pipe 详解
NestJS 提供的内置 Pipe:
| Pipe | 作用 | 示例 |
|---|---|---|
ParseIntPipe | 转换为整数 | @Query('page', ParseIntPipe) page: number |
ParseFloatPipe | 转换为浮点数 | @Query('price', ParseFloatPipe) price: number |
ParseBoolPipe | 转换为布尔值 | @Query('active', ParseBoolPipe) active: boolean |
ParseArrayPipe | 转换为数组 | @Query('ids', ParseArrayPipe) ids: number[] |
ParseUUIDPipe | 验证 UUID | @Param('id', ParseUUIDPipe) id: string |
ParseEnumPipe | 验证枚举值 | @Query('status', new ParseEnumPipe(Status)) |
DefaultValuePipe | 设置默认值 | @Query('page', new DefaultValuePipe(1)) |
3.3 ParseIntPipe 使用示例
基本用法:
import { Controller, Get, Query, ParseIntPipe } from '@nestjs/common';
@Controller('home')
export class HomeController {
@Get()
async findAll(
@Query('page', ParseIntPipe) page: number = 1,
@Query('size', ParseIntPipe) size: number = 10,
) {
// page 和 size 现在都是 number 类型
return this.homeService.findAll(page, size);
}
}自定义错误消息:
@Get()
async findAll(
@Query('page', new ParseIntPipe({ errorHttpStatusCode: HttpStatus.NOT_ACCEPTABLE }))
page: number,
) {
return this.homeService.findAll(page);
}3.4 DefaultValuePipe 使用示例
设置默认值:
import { Controller, Get, Query, DefaultValuePipe } from '@nestjs/common';
@Controller('home')
export class HomeController {
@Get()
async findAll(
@Query('page', new DefaultValuePipe(1), ParseIntPipe) page: number,
@Query('size', new DefaultValuePipe(10), ParseIntPipe) size: number,
) {
return this.homeService.findAll(page, size);
}
}四、路径别名配置
4.1 配置路径别名
修改 tsconfig.json:
{
"compilerOptions": {
"baseUrl": "./", // 必须设置 baseUrl
"paths": {
"src/*": ["src/*"],
"@/*": ["src/*"]
}
}
}配置说明:
baseUrl:设置根目录,必须配置paths:配置路径别名映射src/*:映射src目录下的所有文件@/*:使用@符号映射src目录
4.2 使用路径别名
使用前:
import { PrismaService } from '../../../prisma/prisma.service';使用后:
// 方式一:使用 src 别名
import { PrismaService } from 'src/prisma/prisma.service';
// 方式二:使用 @ 别名(推荐)
import { PrismaService } from '@/prisma/prisma.service';注意事项:
baseUrl必须设置,否则paths不生效- 路径别名只在 TypeScript 编译时生效
- 运行时仍需要相对路径或绝对路径
五、全局模块设置
5.1 为什么需要全局模块?
问题场景:
PrismaModule需要在所有功能模块中使用- 每个模块都要导入
PrismaModule,非常繁琐 - 容易遗漏导入,导致依赖注入失败
传统方式(繁琐):
// 每个模块都要导入 PrismaModule
@Module({
imports: [PrismaModule],
providers: [HomeService],
controllers: [HomeController],
})
export class HomeModule {}5.2 使用 @Global 装饰器
设置全局模块:
// src/prisma/prisma.module.ts
import { Global, Module } from '@nestjs/common';
import { PrismaService } from './prisma.service';
@Global() // 标记为全局模块
@Module({
providers: [PrismaService],
exports: [PrismaService], // 必须导出
})
export class PrismaModule {}使用全局模块:
// 其他模块无需导入 PrismaModule
@Module({
providers: [HomeService],
controllers: [HomeController],
})
export class HomeModule {}Service 中直接使用:
// src/modules/home/home.service.ts
import { Injectable } from '@nestjs/common';
import { PrismaService } from '@/prisma/prisma.service';
@Injectable()
export class HomeService {
constructor(private prisma: PrismaService) {}
// 无需导入 PrismaModule,直接使用 PrismaService
async findAll(page: number, size: number) {
return this.prisma.homeResources.findMany();
}
}5.3 全局模块注意事项
全局模块使用规则:
│
├── 1. 使用 @Global() 装饰器
│ └── 在 @Module() 装饰器之前
│
├── 2. 必须导出 providers
│ └── exports: [PrismaService]
│
├── 3. 无需重复导入
│ └── 其他模块无需 imports: [PrismaModule]
│
├── 4. 全局模块只需注册一次
│ └── 通常在 AppModule 中导入
│
└── 5. 谨慎使用
├── 只对真正全局的服务使用
└── 例如:PrismaService、ConfigService、LoggerService六、Prisma 分页查询详解
6.1 Prisma 分页参数
findMany() 分页参数:
interface FindManyOptions {
skip?: number; // 跳过多少条
take?: number; // 获取多少条
}分页计算公式:
// 前端传递:page(页码)、size(每页条数)
// Prisma 需要:skip(跳过条数)、take(获取条数)
const skip = (page - 1) * size; // 跳过的条数
const take = size; // 获取的条数
// 示例:
// page=1, size=10 → skip=0, take=10
// page=2, size=10 → skip=10, take=10
// page=3, size=10 → skip=20, take=106.2 完整的分页查询实现
Service 层:
// src/modules/home/home.service.ts
import { Injectable } from '@nestjs/common';
import { PrismaService } from '@/prisma/prisma.service';
@Injectable()
export class HomeService {
constructor(private prisma: PrismaService) {}
async findAll(page: number = 1, size: number = 10) {
// 计算分页参数
const skip = (page - 1) * size;
const take = size;
// 查询数据和总数
const [data, total] = await this.prisma.$transaction([
this.prisma.homeResources.findMany({
skip,
take,
orderBy: { order: 'asc' },
}),
this.prisma.homeResources.count(),
]);
return [data, total];
}
}Controller 层:
// src/modules/home/home.controller.ts
import { Controller, Get, Query, ParseIntPipe } from '@nestjs/common';
import { HomeService } from './home.service';
@Controller('home')
export class HomeController {
constructor(private readonly homeService: HomeService) {}
@Get()
async findAll(
@Query('page', ParseIntPipe) page: number = 1,
@Query('size', ParseIntPipe) size: number = 10,
) {
const [data, total] = await this.homeService.findAll(page, size);
return { data, total };
}
}响应格式:
{
"data": [
{
"id": 1,
"title": "资源1",
"order": 100
},
{
"id": 2,
"title": "资源2",
"order": 200
}
],
"total": 100
}七、Prisma 事务处理
7.1 事务的作用
事务(Transaction)的作用:
│
├── 1. 原子性(Atomicity)
│ ├── 一系列操作要么全部成功,要么全部失败
│ └── 部分失败会回滚已成功的操作
│
├── 2. 一致性(Consistency)
│ ├── 数据库状态保持一致
│ └── 事务前后数据完整性不变
│
├── 3. 隔离性(Isolation)
│ ├── 多个事务互不干扰
│ └── 并发执行结果与串行执行一致
│
└── 4. 持久性(Durability)
├── 事务完成后数据永久保存
└── 即使系统故障也不丢失7.2 Prisma $transaction 使用
基本语法:
const [result1, result2, ...] = await prisma.$transaction([
prisma.model1.operation1(),
prisma.model2.operation2(),
...
]);使用场景一:查询数据 + 统计总数:
// 同时查询数据和总数
const [data, total] = await this.prisma.$transaction([
this.prisma.homeResources.findMany({
skip: 0,
take: 10,
}),
this.prisma.homeResources.count(),
]);
// data: Array<HomeResources>
// total: number使用场景二:创建订单 + 扣减库存:
const [order, inventory] = await this.prisma.$transaction([
// 创建订单
this.prisma.order.create({
data: { userId: 1, productId: 1, quantity: 2 },
}),
// 扣减库存
this.prisma.inventory.update({
where: { productId: 1 },
data: { stock: { decrement: 2 } },
}),
]);
// 如果库存不足,订单创建也会回滚使用场景三:批量操作:
// 批量创建用户
const result = await this.prisma.$transaction(
users.map(user =>
this.prisma.user.create({ data: user })
)
);7.3 $transaction 注意事项
// 正确:数组形式(并行执行)
const [data, total] = await this.prisma.$transaction([
this.prisma.user.findMany(),
this.prisma.user.count(),
]);
// 正确:回调形式(顺序执行,可回滚)
const result = await this.prisma.$transaction(async (tx) => {
// 创建用户
const user = await tx.user.create({ data: { name: 'Tom' } });
// 创建用户配置
const config = await tx.userConfig.create({
data: { userId: user.id, theme: 'dark' },
});
return { user, config };
});
// 错误:事务中不能使用非事务的 prisma 实例
await this.prisma.$transaction(async (tx) => {
await tx.user.create({ data: { name: 'Tom' } });
await this.prisma.userConfig.create({ data: { theme: 'dark' } }); // 错误!
});八、Controller 与 Service 分层设计
8.1 为什么需要分层?
分层设计的优势:
│
├── 1. 职责分离
│ ├── Controller:处理 HTTP 请求、响应
│ ├── Service:处理业务逻辑
│ └── Repository:处理数据访问
│
├── 2. 可读性强
│ ├── 代码结构清晰
│ ├── 职责明确
│ └── 易于理解
│
├── 3. 复用性强
│ ├── Service 可在多个 Controller 中使用
│ ├── 避免重复代码
│ └── 易于测试
│
└── 4. 易于维护
├── 修改业务逻辑只改 Service
├── 修改接口只改 Controller
└── 互不影响8.2 Controller 层职责
@Controller('home')
export class HomeController {
constructor(private readonly homeService: HomeService) {}
@Get()
async findAll(
@Query('page', ParseIntPipe) page: number = 1,
@Query('size', ParseIntPipe) size: number = 10,
) {
// Controller 层职责:
// 1. 获取请求参数
// 2. 调用 Service 方法
// 3. 封装响应数据
// 4. 返回给前端
const [data, total] = await this.homeService.findAll(page, size);
return { data, total }; // 封装响应格式
}
}Controller 层职责清单:
| 职责 | 说明 | 示例 |
|---|---|---|
| 获取参数 | 使用装饰器获取请求参数 | @Query(), @Body(), @Param() |
| 参数转换 | 使用 Pipe 转换参数类型 | ParseIntPipe, ParseBoolPipe |
| 参数校验 | 使用 DTO 校验参数 | class-validator |
| 调用 Service | 将参数传递给 Service | this.homeService.findAll(page, size) |
| 封装响应 | 将数据封装为统一格式 | { data, total } |
| 处理异常 | 捕获并处理异常 | try-catch |
8.3 Service 层职责
@Injectable()
export class HomeService {
constructor(private prisma: PrismaService) {}
async findAll(page: number = 1, size: number = 10) {
// Service 层职责:
// 1. 业务逻辑处理(分页计算)
// 2. 调用数据库(Prisma)
// 3. 数据处理(事务)
// 4. 返回原始数据给 Controller
const skip = (page - 1) * size;
const take = size;
const [data, total] = await this.prisma.$transaction([
this.prisma.homeResources.findMany({
skip,
take,
orderBy: { order: 'asc' },
}),
this.prisma.homeResources.count(),
]);
return [data, total]; // 返回原始数据
}
}Service 层职责清单:
| 职责 | 说明 | 示例 |
|---|---|---|
| 业务逻辑 | 实现具体业务逻辑 | 分页计算、数据过滤 |
| 数据访问 | 调用数据库操作 | prisma.findMany() |
| 事务处理 | 管理数据库事务 | $transaction() |
| 数据转换 | 数据格式转换 | DTO → Entity |
| 缓存处理 | 处理缓存逻辑 | Redis 缓存 |
| 异常处理 | 抛出业务异常 | NotFoundException |
8.4 为什么要分离 Controller 和 Service?
** 不推荐:将逻辑写在 Controller 中**:
@Get()
async findAll(@Query('page') page: string, @Query('size') size: string) {
// 不推荐:Controller 中写业务逻辑
const pageNum = parseInt(page);
const sizeNum = parseInt(size);
const skip = (pageNum - 1) * sizeNum;
const take = sizeNum;
const [data, total] = await this.prisma.$transaction([
this.prisma.homeResources.findMany({ skip, take }),
this.prisma.homeResources.count(),
]);
return { data, total };
}** 推荐:Controller 和 Service 分离**:
// Controller:只负责接收请求和响应
@Get()
async findAll(
@Query('page', ParseIntPipe) page: number = 1,
@Query('size', ParseIntPipe) size: number = 10,
) {
const [data, total] = await this.homeService.findAll(page, size);
return { data, total };
}
// Service:负责业务逻辑
async findAll(page: number = 1, size: number = 10) {
const skip = (page - 1) * size;
const take = size;
const [data, total] = await this.prisma.$transaction([
this.prisma.homeResources.findMany({ skip, take }),
this.prisma.homeResources.count(),
]);
return [data, total];
}分层的好处:
分层设计的好处:
│
├── 1. 可读性
│ ├── Controller:清晰看到所有接口
│ ├── Service:清晰看到所有业务逻辑
│ └── 易于理解和维护
│
├── 2. 复用性
│ ├── HomeService 可在其他模块使用
│ ├── 例如:在 OrderService 中使用 HomeService
│ └── 避免重复代码
│
├── 3. 可测试性
│ ├── 单独测试 Service 层
│ ├── 单独测试 Controller 层
│ └── 易于编写单元测试
│
└── 4. 可维护性
├── 修改业务逻辑只改 Service
├── 修改接口格式只改 Controller
└── 互不影响九、DI 系统工作原理回顾
9.1 DI 系统核心概念
依赖注入(DI)工作原理:
│
├── 第一步:在 constructor 中声明依赖
│ └── constructor(private homeService: HomeService) {}
│
├── 第二步:DI 系统查找依赖
│ ├── 检查当前模块的 providers
│ ├── 检查导入模块的 exports
│ └── 检查全局模块的 exports
│
├── 第三步:DI 系统创建实例
│ ├── 单例模式:只创建一次
│ ├── 自动注入依赖
│ └── 保存到 DI 容器中
│
└── 第四步:使用实例
└── this.homeService.findAll()9.2 constructor 的作用
constructor 中声明依赖:
@Injectable()
export class HomeService {
// 在 constructor 中声明依赖
constructor(private prisma: PrismaService) {}
// 等价于:
// this.prisma = new PrismaService();
// 但是由 DI 系统自动完成
}等价的传统写法:
// 传统写法:手动创建实例
export class HomeService {
private prisma: PrismaService;
constructor() {
this.prisma = new PrismaService();
}
}
// DI 写法:自动注入实例
@Injectable()
export class HomeService {
constructor(private prisma: PrismaService) {}
}DI 的优势:
DI 系统的优势:
│
├── 1. 解耦
│ └── 不需要手动创建实例
│
├── 2. 单例
│ └── 全局共享一个实例
│
├── 3. 自动注入
│ └── 自动解析和注入依赖
│
└── 4. 易于测试
└── 可以轻松替换依赖进行测试9.3 跨模块使用 Service
场景:在 HomeModule 中使用 PrismaService
方式一:导入 PrismaModule(传统方式):
// home.module.ts
@Module({
imports: [PrismaModule], // 导入 PrismaModule
providers: [HomeService],
controllers: [HomeController],
})
export class HomeModule {}方式二:使用全局模块(推荐):
// prisma.module.ts
@Global()
@Module({
providers: [PrismaService],
exports: [PrismaService],
})
export class PrismaModule {}
// home.module.ts
@Module({
// 无需导入 PrismaModule
providers: [HomeService],
controllers: [HomeController],
})
export class HomeModule {}十、完整实战示例
10.1 项目结构
src/
├── prisma/
│ ├── prisma.module.ts # 全局模块
│ └── prisma.service.ts # Prisma 服务
├── modules/
│ └── home/
│ ├── home.module.ts
│ ├── home.controller.ts
│ ├── home.service.ts
│ └── dto/
│ ├── create-home-resource.dto.ts
│ └── update-home-resource.dto.ts
├── app.module.ts
└── main.ts10.2 完整代码清单
1. PrismaModule(全局模块):
// src/prisma/prisma.module.ts
import { Global, Module } from '@nestjs/common';
import { PrismaService } from './prisma.service';
@Global()
@Module({
providers: [PrismaService],
exports: [PrismaService],
})
export class PrismaModule {}2. PrismaService:
// src/prisma/prisma.service.ts
import { Injectable, OnModuleInit } from '@nestjs/common';
import { PrismaClient } from '@prisma/client';
@Injectable()
export class PrismaService extends PrismaClient implements OnModuleInit {
async onModuleInit() {
await this.$connect();
}
}3. HomeModule:
// src/modules/home/home.module.ts
import { Module } from '@nestjs/common';
import { HomeController } from './home.controller';
import { HomeService } from './home.service';
@Module({
controllers: [HomeController],
providers: [HomeService],
})
export class HomeModule {}4. HomeController:
// src/modules/home/home.controller.ts
import { Controller, Get, Query, ParseIntPipe } from '@nestjs/common';
import { HomeService } from './home.service';
@Controller('home')
export class HomeController {
constructor(private readonly homeService: HomeService) {}
@Get()
async findAll(
@Query('page', ParseIntPipe) page: number = 1,
@Query('size', ParseIntPipe) size: number = 10,
) {
const [data, total] = await this.homeService.findAll(page, size);
return { data, total };
}
}5. HomeService:
// src/modules/home/home.service.ts
import { Injectable } from '@nestjs/common';
import { PrismaService } from '@/prisma/prisma.service';
@Injectable()
export class HomeService {
constructor(private prisma: PrismaService) {}
async findAll(page: number = 1, size: number = 10) {
const skip = (page - 1) * size;
const take = size;
const [data, total] = await this.prisma.$transaction([
this.prisma.homeResources.findMany({
skip,
take,
orderBy: { order: 'asc' },
}),
this.prisma.homeResources.count(),
]);
return [data, total];
}
}6. AppModule:
// src/app.module.ts
import { Module } from '@nestjs/common';
import { PrismaModule } from './prisma/prisma.module';
import { HomeModule } from './modules/home/home.module';
@Module({
imports: [PrismaModule, HomeModule],
})
export class AppModule {}10.3 测试示例
请求测试:
# 查询第一页,每页 10 条
GET http://localhost:3000/home?page=1&size=10
# 响应
{
"data": [
{
"id": 1,
"title": "资源1",
"order": 100
},
{
"id": 2,
"title": "资源2",
"order": 200
}
],
"total": 100
}分页计算验证:
# page=1, size=10 → skip=0, take=10
# 返回第 1-10 条数据
# page=2, size=10 → skip=10, take=10
# 返回第 11-20 条数据
# page=3, size=10 → skip=20, take=10
# 返回第 21-30 条数据十一、常见问题与解决方案
11.1 Query 参数类型问题
问题:@Query() 获取的参数类型是 string,不是 number。
解决方案:使用 ParseIntPipe 转换。
// 错误:直接使用,类型是 string
@Query('page') page: number
// 正确:使用 ParseIntPipe 转换
@Query('page', ParseIntPipe) page: number
// 正确:设置默认值
@Query('page', ParseIntPipe) page: number = 111.2 路径别名不生效
问题:配置了 tsconfig.json 的 paths,但路径别名不生效。
解决方案:确保 baseUrl 已设置。
{
"compilerOptions": {
"baseUrl": "./", // 必须设置
"paths": {
"@/*": ["src/*"]
}
}
}11.3 PrismaService 注入失败
问题:PrismaService 依赖注入失败。
解决方案一:导入 PrismaModule:
@Module({
imports: [PrismaModule],
providers: [HomeService],
})
export class HomeModule {}解决方案二:使用全局模块:
@Global()
@Module({
providers: [PrismaService],
exports: [PrismaService],
})
export class PrismaModule {}11.4 分页计算错误
问题:分页查询返回的数据不正确。
常见错误:
// 错误:skip 计算错误
const skip = page * size; // 应该是 (page - 1) * size
// 正确:skip 计算
const skip = (page - 1) * size;十二、学习要点总结
12.1 核心知识点
本节核心知识点:
│
├── 完整链路实现
│ ├── Controller → Service → Prisma → Database
│ ├── 参数传递流程
│ └── 响应数据封装
│
├── 分页查询
│ ├── 获取 page 和 size 参数
│ ├── 使用 ParseIntPipe 转换类型
│ ├── 计算 skip 和 take
│ └── 使用 $transaction 查询数据和总数
│
├── Pipes(管道)
│ ├── 数据转换(string → number)
│ ├── 数据校验(后续学习)
│ └── 内置 Pipe:ParseIntPipe、DefaultValuePipe
│
├── 路径别名
│ ├── tsconfig.json 配置 baseUrl 和 paths
│ └── 使用 @/* 替代相对路径
│
├── 全局模块
│ ├── @Global() 装饰器
│ ├── 无需重复导入
│ └── PrismaModule 设置为全局模块
│
├── Prisma 事务
│ ├── $transaction() 的作用
│ ├── 原子性、一致性
│ └── 查询数据 + 统计总数
│
└── DI 系统
├── constructor 中声明依赖
├── DI 系统自动注入实例
└── 单例模式12.2 学习路径规划
学习路径:
│
├── 第一阶段:理解概念(1 天)
│ ├── 理解完整链路流程
│ ├── 理解 Pipes 的作用
│ └── 理解 DI 系统原理
│
├── 第二阶段:实践使用(2-3 天)
│ ├── 实现完整的分页查询
│ ├── 配置路径别名
│ └── 使用全局模块
│
└── 第三阶段:深入应用(持续)
├── 自定义 Pipe(下一节)
├── 参数校验(下一节)
└── 复杂事务处理12.3 重要程度标注
重要程度说明:
│
├── 必须掌握
│ ├── 完整链路实现流程
│ ├── 分页查询实现
│ ├── ParseIntPipe 的使用
│ ├── 全局模块的设置
│ └── Prisma $transaction 的使用
│
├── 重要
│ ├── 路径别名配置
│ ├── Controller 和 Service 分层设计
│ ├── DI 系统工作原理
│ └── 分页计算公式
│
└── 了解
├── Pipe 的其他类型
├── 事务的高级用法
└── $transaction 的回调形式重要提示:这一节是 NestJS 从 Controller 到数据库完整链路的核心内容,理解分页查询、参数转换、全局模块、Prisma 事务,对实际项目开发非常重要!特别是要掌握 ParseIntPipe 和 $transaction() 的使用,这在实际项目中非常实用!
下一节预告:下一节将学习自定义 Pipe 和参数校验,包括 class-validator 和 class-transformer 的使用。