You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

如何修复Nest.js + GraphQL中的错误输出格式问题

Nest.js GraphQL 统一错误格式问题

在Nest.js中使用GraphQL时,数据验证失败后的错误格式不符合预期。已配置全局异常过滤器和gqlErrorHandler,但GraphQL仍将错误信息包裹在data和originalError中,无法得到自定义格式的输出。


现有配置代码

1. GraphQLModule 配置

GraphQLModule.forRoot<ApolloDriverConfig>({
      driver: ApolloDriver,
      playground: false,
      plugins: [ApolloServerPluginLandingPageLocalDefault()],
      typePaths: ['./**/*.graphql'],
      resolvers: { DateTime: GraphQLDateTime },
      context: ({ req, res }) => ({ req, res }),
      formatError: gqlErrorHandler,
    }),

2. gqlErrorHandler 实现

import { GraphQLError } from 'graphql';

export const gqlErrorHandler = (error: GraphQLError) => {
  return error;
};

3. 全局异常过滤器代码

import {
  ExceptionFilter,
  Catch,
  ArgumentsHost,
  HttpException,
  HttpStatus,
} from '@nestjs/common';
import { Request, Response } from 'express';

@Catch()
export class AllExceptionsFilter<T> implements ExceptionFilter {
  catch(exception: T, 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;

    if (['graphql'].includes(host.getType())) {
      throw new HttpException(
        this._response(status, request, exception),
        status,
      );
    }

    response.status(status).json(this._response(status, request, exception));
  }

  private _response(status: number, request: Request, exception: any) {
    return {
      statusCode: status,
      timestamp: new Date().toISOString(),
      path: request?.url,
      method: request?.method,
      params: request?.params,
      query: request?.query,
      exception: {
        name: exception['name'],
        message: exception['message'],
      },
    };
  }
}

当前错误输出

{
  "data": {},
  "errors": [
    {
      "message": "Http Exception",
      "locations": [
        {
          "line": 2,
          "column": 3
        }
      ],
      "path": [
        "createArticle"
      ],
      "extensions": {
        "code": "BAD_REQUEST",
        "stacktrace": [
          "HttpException: Http Exception",
          "    at AllExceptionsFilter.catch (/usr/src/app/src/exceptions/all-exceptions.filter.ts:22:13)",
          "    at ExternalExceptionsHandler.invokeCustomFilters (/usr/src/app/node_modules/@nestjs/core/exceptions/external-exceptions-handler.js:34:32)",
        ],
        "originalError": {
          "statusCode": 400,
          "exception": {
            "name": "BadRequestException",
            "message": "Bad Request Exception"
          }
        }
      }
    }
  ]
}

解决方案

步骤1:修改全局异常过滤器,适配GraphQL上下文

不要在GraphQL上下文里重新抛出HttpException,而是直接构造自定义错误结构,同时保留GraphQL所需的上下文信息:

import {
  ExceptionFilter,
  Catch,
  ArgumentsHost,
  HttpException,
  HttpStatus,
} from '@nestjs/common';
import { Request, Response } from 'express';
import { GqlArgumentsHost } from '@nestjs/graphql';

@Catch()
export class AllExceptionsFilter<T> implements ExceptionFilter {
  catch(exception: T, host: ArgumentsHost) {
    // 处理GraphQL上下文
    if (host.getType() === 'graphql') {
      const gqlHost = GqlArgumentsHost.create(host);
      const request = gqlHost.getContext().req as Request;
      const status = exception instanceof HttpException 
        ? exception.getStatus() 
        : HttpStatus.INTERNAL_SERVER_ERROR;
      
      // 直接抛出包含自定义格式的异常,交给formatError处理
      throw new HttpException(this._response(status, request, exception), status);
    }

    // HTTP上下文原有逻辑不变
    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;

    response.status(status).json(this._response(status, request, exception));
  }

  private _response(status: number, request: Request, exception: any) {
    return {
      statusCode: status,
      timestamp: new Date().toISOString(),
      path: request?.url,
      method: request?.method,
      params: request?.params,
      query: request?.query,
      exception: {
        name: exception['name'],
        message: exception['message'],
      },
    };
  }
}

步骤2:完善gqlErrorHandler,提取自定义错误格式

在formatError中提取originalError里的自定义结构,覆盖GraphQL默认错误输出:

import { GraphQLError } from 'graphql';

export const gqlErrorHandler = (error: GraphQLError) => {
  // 提取自定义错误结构
  if (error.extensions?.originalError) {
    const customError = error.extensions.originalError;
    return {
      ...customError,
      locations: error.locations,
      path: error.path,
    };
  }
  // 非自定义错误保持默认处理
  return error;
};

步骤3:确保全局过滤器注册生效

在main.ts中注册全局过滤器:

import { NestFactory } from '@nestjs/core';
import { AppModule } from './app.module';
import { AllExceptionsFilter } from './exceptions/all-exceptions.filter';

async function bootstrap() {
  const app = await NestFactory.create(AppModule);
  app.useGlobalFilters(new AllExceptionsFilter());
  await app.listen(3000);
}
bootstrap();

预期错误输出

修改后,错误将直接返回自定义格式:

{
  "errors": [
    {
      "statusCode": 400,
      "timestamp": "2024-05-20T12:34:56.789Z",
      "path": "/graphql",
      "method": "POST",
      "params": {},
      "query": {},
      "exception": {
        "name": "BadRequestException",
        "message": "Bad Request Exception"
      },
      "locations": [
        {
          "line": 2,
          "column": 3
        }
      ],
      "path": [
        "createArticle"
      ]
    }
  ],
  "data": null
}

内容的提问来源于stack exchange,提问作者Aslero

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.07.26 08:15:41