在 nestjs 中,若在服务层 `return` 一个 `httpexception` 实例(如 `forbiddenexception`),框架不会自动将其视为异常处理流程,而是序列化为响应体并默认返回 201(post)或 200 状态码;必须使用 `throw` 才能触发异常过滤器并返回正确的 http 状态。
NestJS 的异常处理机制高度依赖异常的抛出(throw)而非返回(return)。当你在服务方法中写 return new ForbiddenException('...'),NestJS 会将该对象当作普通响应数据进行序列化——此时控制器接收到的是一个 JavaScript 对象,而非未捕获的异常,因此全局异常过滤器(如 BaseExceptionFilter)完全不会介入,最终响应状态码由 NestJS 的默认行为决定:对 POST 请求返回 201 Created,其他则多为 200 OK,与业务语义严重不符。
✅ 正确做法是:所有业务校验失败都应 throw 异常,而非 return 异常实例。例如:
async login(dto: LoginDto){ const user = await this.prisma.user.findUnique({ where: { email: dto.email }, }); if (!user) { throw new UnauthorizedException('Invalid credentials'); // ✅ 抛出,非返回 } const pwMatches = await argon.verify(user.password, dto.password); if (!pwMatches) { throw new UnauthorizedException('Invalid credentials'); // ✅ 统一使用 Unauthorized 更语义准确 } return this.signToken(user.id, user.email); // ✅ 正常成功路径返回数据 }
⚠️ 注意事项:
} catch (error) {
this.logger.error(`Login failed for ${dto.email}`, error.stack);
throw new InternalServerErrorException('Authentication service unavailable');
}总结:NestJS 的 HTTP 状态码由异常是否被抛出并被捕获决定,而非由你返回什么对象决定。始终用 throw 触发异常流,让框架统一处理状态码、响应格式与日志,这是构建健壮、符合 REST 规范 API 的关键实践。