十年匠心定制 · 商业建站与技术教学双线并行 咨询热线:400-886-1026 service@lmnt.cn
ARTICLE DETAIL

资讯详情

深耕网站建设与运营推广的一线实战洞察。

NestJS 入门(5):Pipe 与 DTO 校验

NestJS 入门(5):Pipe 与 DTO 校验 NestJS 入门5Pipe 与 DTO 校验上一篇NestJS 入门4统一响应与异常处理 讲了成功/失败统一信封。这篇文章解决下一个常见误解Controller 里写了Body() body: { email: string; password: string }是不是请求体就一定合法不是。TypeScript 类型只在编译期存在运行时 Nest 不会按这个接口帮你校验。脏数据可以一路进到 Service直到你自己if判断或数据库报错。Pipe管道就是 Nest 用来在「进业务之前」做转换与校验的地方。1. Pipe 在请求链路里的位置回顾整条链请求进来 → Middleware → Guard能不能进 → Pipe参数转成什么、合不合法 → Controller → Service对照职责组件回答的问题Guard你是谁有没有权限Pipe参数是什么类型缺没缺字段值合不合法Filter出错后对外长什么样一句话Guard 管门禁Pipe 管入参Filter 管出场。2. 没有 Pipe 时会发生什么登录接口常见写法Post(login)login(Body()body:{email:string;password:string}){returnthis.authService.login(body.email,body.password);}客户端可以发{email:123,password:null}甚至{}TypeScript 类型标注挡不住。body.email可能是undefined校验逻辑若散落在 Service 各处容易漏。很多项目会在 Service 里补if(!name?.trim()||!profile?.trim()){thrownewBadRequestException(name 与 profile 不能为空);}if(!Number.isInteger(chapterNo)||chapterNo1){thrownewBadRequestException(chapterNo 必须为正整数);}这能用但属于「进了业务层才发现入参坏了」。Pipe DTO 的目标是把这类问题尽量拦在 Controller 门口。3. DTO先把「期望的入参」写成类DTOData Transfer Object就是「传输数据结构」。在 Nest 里通常写成class不是 interface因为运行时需要元数据给校验器用。import{IsEmail,IsString,MinLength}fromclass-validator;exportclassLoginDto{IsEmail({},{message:email 格式不正确})email!:string;IsString()MinLength(6,{message:password 至少 6 位})password!:string;}Controller 改成Post(login)login(Body()body:LoginDto){returnthis.authService.login(body.email,body.password);}还差一步启用校验管道否则装饰器不会自动生效。4. ValidationPipe让装饰器真正跑起来安装npmi class-validator class-transformer全局启用推荐asyncfunctionbootstrap(){constappawaitNestFactory.create(AppModule);app.useGlobalPipes(newValidationPipe({whitelist:true,// 去掉 DTO 未声明的字段forbidNonWhitelisted:true,// 多传未知字段直接 400transform:true,// 自动把普通对象转成 DTO 类实例transformOptions:{enableImplicitConversion:true,// 如 query 里的 12 → number},}));app.useGlobalInterceptors(newResponseInterceptor());app.useGlobalFilters(newHttpExceptionFilter());awaitapp.listen(3000);}几个开关的直觉选项作用whitelist只保留 DTO 上声明过的属性多出来的悄悄丢掉forbidNonWhitelisted多传字段直接报错更严防「偷偷塞字段」transform把 JSON 变成LoginDto实例校验装饰器才好使也可以只挂在某个参数上Post(login)login(Body(ValidationPipe)body:LoginDto){returnthis.authService.login(body.email,body.password);}入门项目更常见全局挂一次。5. 校验失败后如何变成统一错误包ValidationPipe校验失败会抛BadRequestExceptionHTTP 400。若你已按上一篇挂了全局 Exception Filter它会被收成统一信封例如{code:1503,msg:email 格式不正确,data:null}注意Nest 默认的校验错误message可能是字符串数组。Filter 里若只按string取可能要再归一一下functionnormalizeMessage(raw:unknown):string{if(typeofrawstring)returnraw;if(Array.isArray(raw))returnraw.join(; );if(rawtypeofrawobjectmessagein(rawasobject)){returnnormalizeMessage((rawas{message:unknown}).message);}returnBad Request;}这样「DTO 校验失败」和「Service 里throw new BadRequestException(...)」对外看起来一致。6. 不只 BodyParam / Query 也能走 Pipe路径参数常常是字符串12业务要的是数字12Get(:id)findOne(Param(id,ParseIntPipe)id:number){returnthis.projectsService.findOne(id);}ParseIntPipe做两件事转换成 number转不了就抛 400自定义范围也可以自己写 Pipe但入门先会用内置的ParseIntPipeParseBoolPipeParseUUIDPipeDefaultValuePipe7. 一张图脏请求在哪被拦住POST /api/auth/login Body: { email: not-an-email, password: 1, extra: true } → Guard若有放行登录接口 → ValidationPipe - extra 被 forbidNonWhitelisted 拒绝或被 whitelist 丢掉 - email / password 不满足规则 → BadRequestException → ExceptionFilter → { code: 1503, msg: ..., data: null } → Controller / Service 根本不会执行对比「只在 Service 校验」请求进 Controller → 进 Service → if 判断 → BadRequestException → Filter两种都能工作。差别是方式优点注意点Pipe DTO规则集中、进业务前就拦、可复用要引入 class-validatorDTO 要维护Service 内校验灵活、贴近领域规则容易散落、重复、漏检实战里常常是格式/必填走 DTO复杂业务规则仍在 Service。例如「章节号必须为正整数」可以 DTO「该章节正文为空无法生成摘要」更适合 Service。8. 最小可跑示例8.1 DTO// create-document.dto.tsimport{IsOptional,IsString,MinLength}fromclass-validator;exportclassCreateDocumentDto{IsString()MinLength(1,{message:title 不能为空})title!:string;IsString()MinLength(1,{message:content 不能为空})content!:string;IsOptional()IsString()docType?:string;}8.2 ControllerPost(api/projects/:projectId/documents)create(Param(projectId)projectId:string,Body()data:CreateDocumentDto){returnthis.documentsService.create(projectId,data);}8.3 全局 Pipemain.tsapp.useGlobalPipes(newValidationPipe({whitelist:true,forbidNonWhitelisted:true,transform:true,}));用错误 body 打一次应在进 Service 前收到 400 统一错误包。9. 常见坑用了 interface 当 DTO运行时没有装饰器元数据ValidationPipe校验不了。要用 class。忘了transform: true进来的仍是普通对象部分场景校验/转换不稳定。以为 TypeScript 类型等于运行时校验这是最常见的误判。校验错误信息是数组Filter 没处理前端可能看到奇怪的msg。所有规则都塞进 DTO领域规则库存够不够、章节是否存在仍应在 ServiceDTO 专注「形状与基本约束」。10. 小结Pipe 在 Guard 之后、Controller 之前负责转换 校验Body() body: { email: string }不会在运行时保证字段合法标准做法DTO class class-validatorValidationPipe校验失败抛 400再经 Exception Filter 变成统一{ code, msg, data }Service 内BadRequestException仍然适合复杂业务规则对照本系列Controller / Service / Module依赖从哪注入有没有 Guard成功谁包装、失败谁整形入参有没有 Pipe / DTO脏数据会在哪一层被拦住下一篇会讲Module 边界与导出——为什么 A 模块注入不了 B 的 Service以及exports/imports如何划清能力边界。系列导航上一篇NestJS 入门4统一响应与异常处理下一篇NestJS 入门6Module 边界与导出第三篇NestJS 入门3Guard 如何挡住未登录请求第二篇NestJS 入门2依赖注入到底解决了什么问题第一篇NestJS 入门1先搞懂 Module、Controller、Service
返回列表