Nest.js 参数校验与自定义返回格式实战指南

作者:小满

时间:2026-10-24

浏览:276

本文详解 Nest.js 中利用 ValidationPipe 结合 class-transformer 解决查询参数类型转换问题,以及通过全局拦截器统一封装 API 成功响应格式。同时介绍隐藏实体敏感字段及全局错误过滤器的配置方法,帮助开发者构建规范的后端接口。

文章目录

0x0 参数校验

在 Nest.js 开发中,参数校验通常借助管道(Pipe)机制完成。尽管官方文档提供了相关说明,但实际编写时可能会遇到一些理解上的障碍或配置难点。

以构建一个包含多个查询参数的接口为例,我们需要定义对应的 DTO 数据结构来接收前端传入的数据。


import { ApiProperty } from '@nestjs/swagger'

export class QueryUserDto {
 @ApiProperty({
 required: false,
 description: '页码'
 })
 readonly currentPage: number

 @ApiProperty({
 required: false,
 description: '条数'
 })
 readonly pageSize: number

 @ApiProperty({
 required: false,
 description: '用户账号'
 })
 readonly username?: string

 @ApiProperty({
 required: false,
 description: '用户状态'
 })
 readonly activeStatus: number

 @ApiProperty({
 required: false,
 description: '排序的方式: ASC, DESC'
 })
 readonly order: 'DESC' | 'ASC'
}
 TYPESCRIPT

当通过 @Query 装饰器接收请求参数时,默认情况下所有传入的数据类型均为 String。若需将其转换为具体的数字或布尔值,必须引入 class-transformer 库并使用其 Type 装饰器进行显式类型转换。


import { ApiProperty } from '@nestjs/swagger'
import { Type } from 'class-transformer'

export class QueryUserDto {
 @ApiProperty({
 required: false,
 description: '页码'
 })
 @Type(() => Number)
 readonly currentPage: number = 1

 @ApiProperty({
 required: false,
 description: '条数'
 })
 @Type(() => Number)
 readonly pageSize: number = 10

 @ApiProperty({
 required: false,
 description: '用户账号'
 })
 readonly username?: string

 @ApiProperty({
 required: false,
 description: '用户状态'
 })
 @Type(() => Number)
 readonly activeStatus: number = 3

 @ApiProperty({
 required: false,
 description: '排序的方式: ASC, DESC'
 })
 readonly order: 'DESC' | 'ASC' = 'DESC'
}

随后,在配置 ValidationPipe 管道时,需开启 transform 选项以启用自动类型转换功能。


app.useGlobalPipes(
 new ValidationPipe({
 transform: true
 })
)

或者,可以在 app.modules.ts 模块配置中通过注入 APP_PIPE 的方式全局注册该管道。


import { ValidationPipe } from '@nestjs/common'
import { APP_PIPE } from '@nestjs/core'

@Module({
 imports: [
 // ...
 ],
 controllers: [AppController],
 providers: [
 {
  provide: APP_PIPE,
  useValue: new ValidationPipe({
  transform: true
  })
 }
 ]
})

这两种配置方式的区别主要在于应用类型是否混合,开发者可根据项目架构选择局部或全局配置。

为简化代码,我通常将上述配置直接写入全局方法中。这样,Service 层接收到的数据已经过管道处理,无需再手动进行繁琐的数据类型判断。

0x1 自定义返回数据格式

默认情况下,Controller 返回的数据结构直接对应数据库表的实体结构,这可能导致接口返回格式不统一。


{
 "id": "d8d5a56c-ee9f-4e41-be48-5414a7a5712c",
 "username": "Akeem.Cremin",
 "password": "$2b$10$kRcsmN6ewFC2GOs0TEg6TuvDbNzf1VGCbQf2fI1UeyPAiZCq9rMKm",
 "email": "Garrett87@hotmail.com",
 "nickname": "Wallace Nicolas",
 "role": "user",
 "isActive": true,
 "createdTime": "2021-03-24T15:24:26.806Z",
 "updatedTime": "2021-03-24T15:24:26.806Z"
}

为了规范 API 响应,通常需要定义统一的返回格式,例如包含状态码、消息体和具体数据对象。


{
 "statusCode": 200,
 "message": "获取成功",
 "data": {
  "id": "d8d5a56c-ee9f-4e41-be48-5414a7a5712c",
  "username": "Akeem.Cremin",
  "password": "$2b$10$kRcsmN6ewFC2GOs0TEg6TuvDbNzf1VGCbQf2fI1UeyPAiZCq9rMKm",
  "email": "Garrett87@hotmail.com",
  "nickname": "Wallace Nicolas",
  "role": "user",
  "isActive": true,
  "createdTime": "2021-03-24T15:24:26.806Z",
  "updatedTime": "2021-03-24T15:24:26.806Z"
 }
}

实现这一目标需要创建一个自定义的成功请求拦截器(Interceptor)。


nest g in shared/interceptor/transform

import { CallHandler, ExecutionContext, Injectable, Logger, NestInterceptor } from '@nestjs/common'
import { Observable } from 'rxjs'
import { map } from 'rxjs/operators'
import { Request } from 'express'

interface Response<T> {
 data: T
}

@Injectable()
export class TransformInterceptor<T> implements NestInterceptor<T, Response<T>> {
 intercept(context: ExecutionContext, next: CallHandler<T>): Observable<any> {
 const request = context.switchToHttp().getRequest<Request>()
 Logger.log(request.url, '正常接口请求')

 return next.handle().pipe(
  map(data => {
  return {
   data: data,
   statusCode: 200,
   message: '请求成功'
  }
  })
 )
 }
}

创建完成后,在 app.module.ts 中引入该拦截器即可生效。


import { ValidationPipe } from '@nestjs/common'
import { APP_INTERCEPTOR } from '@nestjs/core'

import { TransformInterceptor } from '@/shared/interceptor/transform.interceptor'

@Module({
 imports: [
 // ...
 ],
 controllers: [AppController],
 providers: [
 {
  provide: APP_INTERCEPTOR,
  useClass: TransformInterceptor
 }
 ]
})

需要注意的是 APP_INTERCEPTOR 的执行顺序,TransformInterceptor 应优先放置,否则可能因执行顺序问题导致格式化失效。

此外,还需配置错误过滤器以统一处理异常响应。


nest g f shared/filters/httpException

import { ArgumentsHost, Catch, ExceptionFilter, HttpException, Logger } from '@nestjs/common'
import { Response, Request } from 'express'

@Catch(HttpException)
export class HttpExceptionFilter implements ExceptionFilter {
 catch(exception: HttpException, host: ArgumentsHost) {
 const context = host.switchToHttp()
 const response = context.getResponse<Response>()
 const request = context.getRequest<Request>()
 const status = exception.getStatus()
 const message = exception.message

 Logger.log(`${request.url} - ${message}`, '非正常接口请求')

 response.status(status).json({
  statusCode: status,
  message: message,
  path: request.url,
  timestamp: new Date().toISOString()
 })
 }
}

同样在 app.module.ts 中引入错误过滤器,确保异常也能以统一格式返回。


import { ValidationPipe } from '@nestjs/common'
import { APP_FILTER } from '@nestjs/core'

import { HttpExceptionFilter } from '@/shared/filters/http-exception.filter'

@Module({
 imports: [
 // ...
 ],
 controllers: [AppController],
 providers: [
 {
  provide: APP_FILTER,
  useClass: HttpExceptionFilter
 }
 ]
})

0x2 隐藏实体类中的某个字段

虽然可以使用 @Exclude 装饰器来隐藏敏感字段,但在 findAll 等批量查询场景中可能无法灵活满足需求。尽管 Serialization 文档中有详细说明,这里提供一种替代方案:在实体类的敏感字段上添加特定属性。


import { BaseEntity, Entity, Column, PrimaryGeneratedColumn } from 'typeorm'

@Entity('user')
export class UserEntity extends BaseEntity {
 @PrimaryGeneratedColumn('uuid', {
  comment: '用户编号'
 })
 id: string

 @Column({
  type: 'varchar',
  length: 50,
  unique: true,
  comment: '登录用户'
 })
 username: string

 @Column({
  type: 'varchar',
  length: 200,
  select: false,
  comment: '密码'
 })
 password: string

设置 select: false 可使该字段在默认查询结果中被隐藏。但需注意,若业务逻辑确实需要查询该字段(如登录验证),则必须在查询语句中显式添加该字段。例如在 user.service.ts 的登录查询中:


const user = await getRepository(UserEntity)
   .createQueryBuilder('user')
   .where('user.username = :username', { username })
   .addSelect('user.password')
   .getOne()

通过 .addSelect('user.password') 显式指定查询字段,这样该查询将包含 password 字段,而普通查询方法则默认排除它。

总结

本文介绍了 Nest.js 中参数校验、统一返回格式及字段隐藏的实现方式。更多相关技术细节可参考本站往期文章或继续浏览后续内容,感谢支持。

实用工具推荐

电子包浆

绿化、压缩、噪点和虚拟水印,自由调节做旧程度。

使用 ›
图片压缩

选择画质和输出格式,预览压缩前后的大小。

使用 ›
相关文章