NestJS使用拦截器和异常过滤器实现 RESTful API的统一响应格式

引言

一般我们都会返回统一的响应格式,无论是正确还是错误的接口,所以在nest中使用拦截器和异常过滤器实现 RESTful API的统一响应格式,直接进入正题

主题

响应拦截器

在src下创建interceptors/transform.interceptor.ts,也可以用命令nest g in interceptors/transform

typescript 复制代码
import { Injectable, NestInterceptor, ExecutionContext, CallHandler } from '@nestjs/common';
import { Observable } from 'rxjs';
import { map } from 'rxjs/operators';

export interface ResponseResult<T> {
  code: number;
  message: string;
  data: T;
  timestamp: number;
}

@Injectable()
export class TransformInterceptor<T> implements NestInterceptor<T, ResponseResult<T>> {
  intercept(context: ExecutionContext, next: CallHandler): Observable<ResponseResult<T>> {
    return next.handle().pipe(
      map(data:T => ({
        code: 200,
        message: '请求成功',
        data,
        timestamp: Date.now()
      }))
    );
  }
}

然后到main.ts,去全局实例化

javascript 复制代码
import { NestFactory } from '@nestjs/core';
import { AppModule } from './app.module';
import { TransformInterceptor } from './interceptors/transform.interceptor';


async function bootstrap() {
  const app = await NestFactory.create(AppModule);
  
  // 注册全局拦截器
  app.useGlobalInterceptors(new TransformInterceptor());
  
  await app.listen(3000);
}
bootstrap();

创建个user模块,nest g res user --no-spec,我们去user模块写个接口试试

typescript 复制代码
import { Controller, Get, Post, Body, Param, HttpException, HttpStatus } from '@nestjs/common';
import { UserService } from './user.service';

@Controller('users')
export class UserController {
  constructor(private readonly userService: UserService) {}

  @Get()
  findAll() {
    // 直接返回数据,拦截器会处理格式
    return this.userService.findAll();
  }
}

结果是这样的,证明我们响应拦截返回成功了,然后也可以把时间转换成YYYY/MM/DD这种,自由选择

css 复制代码
{
  "code": 200,
  "message": "请求成功",
  "data": [...],
  "timestamp": 1647434353255
}

异常过滤器

在src下创建filters/httpExecption.filter.ts,也可以用命令nest g f filters/httpExecption

ini 复制代码
import { ExceptionFilter, Catch, ArgumentsHost, HttpException, HttpStatus } from '@nestjs/common';
import { Request, Response } from 'express';

@Catch()
export class GlobalExceptionFilter implements ExceptionFilter {
  catch(exception: any, host: ArgumentsHost) {
    const ctx = host.switchToHttp();
    const response = ctx.getResponse<Response>();
    const request = ctx.getRequest<Request>();

    const status = 
      exception instanceof HttpException
        ? exception.getStatus()
        : HttpStatus.INTERNAL_SERVER_ERROR;

    let message = exception.message || '服务器内部错误';
    
    // 如果是HTTP异常,尝试获取更详细的错误信息
    if (exception instanceof HttpException) {
      const exceptionResponse = exception.getResponse();
      if (typeof exceptionResponse === 'object' && 'message' in exceptionResponse) {
        message = exceptionResponse['message'];
      }
    }

    response.status(status).json({
      code: status,
      message,
      timestamp: Date.now(),
      path: request.url,
      data: null
    });
  }
}

然后到main.ts,去全局实例化 app.useGlobalFilters(new HttpExceptionFilter());

我们在去user模块写个请求,可以看到httpStatus其实有很多选项,根据情况去描述

我们在去postman试下接口

typescript 复制代码
import { Controller, Get, Post, Body, Param, HttpException, HttpStatus } from '@nestjs/common';
import { UserService } from './user.service';

@Controller('users')
export class UserController {
  constructor(private readonly userService: UserService) {}

  @Get()
  findAll() {
    // 直接返回数据,拦截器会处理格式
    return this.userService.findAll();
  }

  @Get(':id')
  findOne(@Param('id') id: string) {
    const user = this.userService.findOne(id);
    if (!user) {
      // 抛出异常,异常过滤器会处理
      throw new HttpException('用户不存在', HttpStatus.NOT_FOUND);
    }
    return user;
  }
}

返回是这样的,证明异常处理也成功了

json 复制代码
{
  "code": 404,
  "message": "用户不存在",
  "data": null,
  "timestamp": 1647434367890,
  "path": "/users/123"
}

扩展

后面会研究一下grapql的响应拦截写法,好像不太一样,学起来有点难,马上到

相关推荐
武子康5 分钟前
Build-Your-Own-X 从零构建轻量级事件驱动微框架:嵌入式与物联网场景下的极简实践
人工智能·后端·物联网·ai·c#·大模型·嵌入式
空圆小生9 分钟前
Vue3 + Spring Boot 全栈实战:从零搭建在线彩票模拟系统
java·spring boot·后端
小马爱打代码12 分钟前
SpringBoot + 分布式锁 + 事务日志:跨服务操作原子性兜底方案
spring boot·分布式·后端
Rust研习社14 分钟前
从 LaunchBadge 到 transact-rs:SQLx 社区迈出可持续治理的第一步
开发语言·后端·rust
真实的菜16 分钟前
Spring Boot 2.2.x 优雅停机实践指南
spring boot·后端
宸津-代码粉碎机26 分钟前
Spring AI企业级RAG进阶|文档智能分片调优、ES深度整合、接口限流熔断监控生产实战
java·开发语言·人工智能·后端·spring·elasticsearch·oracle
JustHappy10 小时前
古法编程秘籍(二):什么是代码模块化?别背概念,把房间收拾明白就够了
前端·后端
小江的记录本10 小时前
【JVM虚拟机】堆内存分代模型:年轻代(Eden+Survivor)、老年代、元空间Metaspace(附《思维导图》+《面试高频考点清单》)
java·前端·jvm·后端·python·spring·面试
IT_陈寒13 小时前
Python闭包里藏的这个坑,差点让我加班到凌晨
前端·人工智能·后端
IT_陈寒13 小时前
Java注解空指针?这个坑我踩得莫名其妙
前端·人工智能·后端