Skip to content

输入校验与异常处理

输入校验负责在业务逻辑前拒绝格式错误的外部数据;异常处理负责将业务失败和未知错误转换为安全、一致的 HTTP 响应。

DTO 校验

使用 class-validatorclass-transformer 描述并校验请求输入。

bash
pnpm add class-validator class-transformer
ts
import { IsEmail, IsString, Length } from 'class-validator'

export class CreateUserDto {
  @IsString()
  @Length(1, 64)
  name!: string

  @IsEmail()
  email!: string
}

在入口注册全局 ValidationPipe 后,@Body() input: CreateUserDto 会自动校验。查询参数与路径参数仍是字符串;数值和 UUID 等字段可使用 ParseIntPipeParseUUIDPipe,或为查询 DTO 配合 transform

ts
import { BadRequestException, ValidationPipe } from '@nestjs/common'

app.useGlobalPipes(
  new ValidationPipe({
    whitelist: true,
    forbidNonWhitelisted: true,
    transform: true,
    exceptionFactory: (errors) =>
      new BadRequestException(
        errors.map((error) => ({
          property: error.property,
          messages: Object.values(error.constraints ?? {}),
        })),
      ),
  }),
)

视频中的项目版本将校验错误整理为 propertymessages 数组;这类格式约定集中在 exceptionFactory,Controller 不需要分别处理。

ParseIntPipe 适用于单个路径参数。转换失败时,Pipe 会在 Controller 前直接抛出 400

ts
import { Controller, Get, Param, ParseIntPipe } from '@nestjs/common'

@Controller('users')
export class UsersController {
  @Get(':id')
  findOne(@Param('id', ParseIntPipe) id: number) {
    return { id }
  }
}

Nest 还提供 ParseBoolPipeParseArrayPipe 等内置 Pipe。Pipe 校验输入形状和可转换性;「邮箱是否已被注册」「活动是否可报名」等依赖业务状态的规则放在 Service 中。全局 whitelist 删除 DTO 中未声明字段,forbidNonWhitelisted 将这类字段视为 400 错误。

HTTP 异常

业务层使用 Nest 内置异常表达可预期的客户端错误。框架会将它们转换为对应状态码的 JSON 响应。

ts
import { ConflictException, NotFoundException } from '@nestjs/common'

if (!user) {
  throw new NotFoundException('用户不存在')
}

if (emailExists) {
  throw new ConflictException('邮箱已被使用')
}

常用异常:BadRequestException(400)、UnauthorizedException(401)、ForbiddenException(403)、NotFoundException(404)、ConflictException(409)。

自定义异常过滤器

异常过滤器用于记录异常或统一响应格式。未知异常不应将堆栈、SQL 语句、密钥等内部信息返回给客户端。

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

@Catch()
export class HttpErrorFilter implements ExceptionFilter {
  catch(exception: unknown, host: ArgumentsHost) {
    const context = host.switchToHttp()
    const response = context.getResponse<Response>()
    const request = context.getRequest<Request>()
    const status = exception instanceof HttpException ? exception.getStatus() : HttpStatus.INTERNAL_SERVER_ERROR

    response.status(status).json({
      statusCode: status,
      path: request.url,
      message: status < 500 && exception instanceof HttpException ? exception.message : '服务器内部错误',
    })
  }
}

将过滤器注册为全局过滤器时,优先通过依赖注入提供,以便使用日志、追踪等 Provider;不要只为了统一响应格式而丢失默认异常中的字段和语义。

参考

基于 MIT 许可发布