从0搭建NestJS后端服务(五):统一的响应拦截和异常错误过滤
前言
大家好,我是elk。今天我们要给API接口们统一换装啦!想象一下,当你的女朋友每次出门都穿不同风格的衣服,你是不是更希望她每天穿情侣装呢?后端接口也一样,统一的响应格式能让前端开发更轻松。本文就教大家如何用NestJS给所有接口穿上"统一工装"!
为什么要统一格式?
先看两个典型场景:
场景1:成功响应
json
// 没穿工装
{ "id": 1, "name": "张三" }
// 穿上工装
{
"code": 200,
"message": "请求成功",
"data": { "id": 1, "name": "张三" }
}
场景2:错误响应
json
// 没穿工装
"用户不存在"
// 穿上工装
{
"code": 404,
"message": "用户不存在",
"path": "/users/999",
"timestamp": "2025-03-25T19:00:00Z"
}
统一格式的好处:
- 前端一眼看出请求状态(看code)
- 错误信息结构化,方便调试
- 接口文档更规范
- 客户端处理响应更统一
目录结构
markdown
- src
- common
-filters
-all-exceptions.filter.ts
-Interceptors
-response.interceptor.ts
-interfaces
-api-response.ts
接口定义
api-response.ts
typescript
export interface ApiResponse<T> {
code: number;
message: string;
data?: T;
}
export interface ErrorResponse {
code: number;
message: string;
error: string;
path: string;
timestamp: string;
}
响应拦截器的处理
想象你开了一家快递公司,拦截器就像包装部门,给所有包裹套上统一包装盒。
response.interceptor.ts
typescript
// 从@nestjs/common导入所需的装饰器和接口
import {
CallHandler,
ExecutionContext,
Injectable,
NestInterceptor,
} from '@nestjs/common';
// 导入RxJS的Observable
import { Observable } from 'rxjs';
// 导入RxJS的map操作符
import { map } from 'rxjs/operators';
// 导入自定义的ApiResponse接口
import { ApiResponse } from '@/common/interfaces/api-response';
// 使用Injectable装饰器,将该类标记为可注入的拦截器
@Injectable()
export class ResponseInterceptor<T>
implements NestInterceptor<T, ApiResponse<T>>
{
// 实现拦截器方法,用于处理响应数据
intercept(
context: ExecutionContext, // 执行上下文
next: CallHandler, // 下一个处理程序
): Observable<ApiResponse<T>> {
return next.handle().pipe(
// 使用map操作符转换响应数据
map((data) => ({
code: 200, // 响应状态码
message: '请求成功', // 响应消息
data, // 实际响应数据
})),
);
}
}
异常过滤器的处理
当包裹运输出现问题,客服中心要统一处理投诉反馈。
all-exceptions.filter.ts
typescript
// 从@nestjs/common导入所需的装饰器和接口
import {
ArgumentsHost,
Catch,
ExceptionFilter,
HttpException,
HttpStatus,
} from '@nestjs/common';
// 从express导入Response和Request类型
import { Response, Request } from 'express';
import { ApiResponse } from '../interfaces/api-response';
// 使用Catch装饰器,指定捕获HttpException类型的异常
@Catch(HttpException)
export class AllExceptionsFilter implements ExceptionFilter {
// 实现catch方法,处理捕获的异常
catch(exception: HttpException, host: ArgumentsHost) {
const ctx = host.switchToHttp(); // 获取HTTP上下文
const response = ctx.getResponse<Response>(); // 获取响应对象
const request = ctx.getRequest<Request>(); // 获取请求对象
const status =
exception instanceof HttpException
? exception.getStatus()
: HttpStatus.INTERNAL_SERVER_ERROR; // 获取状态码,默认为500
const message =
exception instanceof HttpException ? exception.message : '服务器错误'; // 获取错误信息,默认为'服务器错误'
// 返回统一的错误响应格式
response.status(status).json({
code: status, // 状态码
message, // 错误信息
timestamp: new Date().toISOString(), // 时间戳
path: request.url, // 请求路径
});
}
}
全局注册
- main.ts
javascript
// 引入全局响应拦截器
import { ResponseInterceptor } from './common/Interceptors/response.interceptor';
// 引入全局异常过滤器
import { AllExceptionsFilter } from '@/common/filters/all-exceptions.filter';
// 全局响应拦截器
app.useGlobalInterceptors(new ResponseInterceptor());
// 全局异常过滤器
app.useGlobalFilters(new AllExceptionsFilter());
案例验证
成功案例:
bash
GET /system/user/1
{
"code": 200,
"message": "请求成功",
"data": {
"id": 1,
"name": "张三"
}
}
失败案例:
bash
GET /system/user/999
{
"code": 404,
"message": "用户不存在",
"path": "/users/999",
"timestamp": "2023-08-15T10:00:00Z"
}
📍 下期预告
《从0搭建NestJS后端服务(六):日志系统的集成》
我们将探讨:
- 如何选择合适日志库
- 如何配置日志级别和输出格式
- 如何实现日志的集中管理
🤝 互动时间
遇到问题?有更好的建议?欢迎留言交流!
- 当接口返回
{ data: null }
时,前端应该如何优雅处理? - 如何设计多层级业务错误码体系(如10001代表手机号已注册)?
- 在微服务架构下,如何保持跨服务异常传递的一致性?
一起交流成长,让开发更高效!🚀