
在 nestjs 中,若在服务层直接 `return` 异常实例(如 `new forbiddenexception()`),框架不会触发异常过滤器,而是将其序列化为响应体,并默认返回 201(post)或 200 状态码,造成鉴权失败却返回成功状态的严重逻辑错误。
这是 NestJS 异常处理机制中一个常见但容易被忽视的关键点:只有被 throw 的异常才会进入 NestJS 的异常处理流程(如全局 ExceptionFilter),进而被正确映射为对应的 HTTP 状态码(如 403);而 return 一个异常对象,仅被视为普通响应数据,框架会照常返回 200/201 状态。
以你的登录服务为例:
async login(dto: LoginDto) {
try {
const user = await this.prisma.user.findUnique({
where: { email: dto.email },
});
if (!user) throw new ForbiddenException('Credentials incorrect'); // ✅ 正确:抛出异常
const pwMatches = await argon.verify(user.password, dto.password);
if (!pwMatches) throw new ForbiddenException('Credentials incorrect'); // ✅ 正确
return this.signToken(user.id, user.email); // ✅ 正常业务返回
} catch (error) {
// ⚠️ 注意:此处不应“吞掉”未知异常并静默返回
// 若此处 return error,则 ForbiddenException 会被当作数据返回,状态码仍是 201
throw error; // ✅ 建议原样 re-throw,交由全局异常过滤器统一处理
}
}✅ 最佳实践总结:
- 永远用 throw,而非 return 异常类实例(如 ForbiddenException, UnauthorizedException);
- 控制器(Controller)应保持简洁,服务(Service)只负责业务逻辑与异常抛出,状态码映射由 NestJS 异常系统自动完成;
- 全局异常过滤器(如 HttpExceptionFilter)可统一格式化错误响应,无需在服务中手动构造错误对象;
- 若需对特定底层错误(如 Prisma 连接异常、数据库超时)做降级处理,应在 catch 中明确判断并 throw 更语义化的 NestJS 异常(如 new InternalServerErrorException('DB unavailable')),而非返回原始错误。
⚠️ 补充提醒:你原代码中 if (!user) return new ForbiddenException(...) 是典型错误模式——它让 NestJS 把异常当成了合法返回值,导致前端收到 { statusCode: 403, message: '...' } 但 HTTP 状态码却是 201 Created,破坏 RESTful 语义,也使前端拦截逻辑失效(例如 Axios 不会进入 .catch())。
遵循「抛出即处理」原则,才能确保 NestJS 的异常流、状态码、日志、监控等能力完整生效。










