主题
输入校验与异常处理
输入校验负责在业务逻辑前拒绝格式错误的外部数据;异常处理负责将业务失败和未知错误转换为安全、一致的 HTTP 响应。
DTO 校验
使用 class-validator 和 class-transformer 描述并校验请求输入。
bash
pnpm add class-validator class-transformerts
import { IsEmail, IsString, Length } from 'class-validator'
export class CreateUserDto {
@IsString()
@Length(1, 64)
name!: string
@IsEmail()
email!: string
}在入口注册全局 ValidationPipe 后,@Body() input: CreateUserDto 会自动校验。查询参数与路径参数仍是字符串;数值和 UUID 等字段可使用 ParseIntPipe、ParseUUIDPipe,或为查询 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 ?? {}),
})),
),
}),
)视频中的项目版本将校验错误整理为 property 与 messages 数组;这类格式约定集中在 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 还提供 ParseBoolPipe、ParseArrayPipe 等内置 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;不要只为了统一响应格式而丢失默认异常中的字段和语义。
