Graphql: GraphQL error handling

Created on 20 Jul 2020  路  11Comments  路  Source: nestjs/graphql

Nest version: lastest.

Throwing errors with Nest HttpException does not play well with GraphQL.

throw new UnauthorizedException() will generate the following:

{
  "errors": [
    {
      "message": "Unauthorized",
      "locations": [
        {
          "line": 2,
          "column": 3
        }
      ],
      "path": [
        "login"
      ],
      "extensions": {
        "code": "INTERNAL_SERVER_ERROR",
        "exception": {
          "response": {
            "statusCode": 401,
            "message": "Unauthorized"
          },
          "status": 401,
          "message": "Unauthorized",
          "stacktrace": [stack]
        }
      }
    }
  ],
  "data": null
}

While throw new AuthenticationError("AuthenticationError") from import { AuthenticationError } from "apollo-server-express" will generate the proper friendly error:

{
  "errors": [
    {
      "message": "AuthenticationError",
      "locations": [
        {
          "line": 2,
          "column": 3
        }
      ],
      "path": [
        "login"
      ],
      "extensions": {
        "code": "UNAUTHENTICATED",
        "exception": {
          "stacktrace": [stack]
        }
      }
    }
  ],
  "data": null
}

When using the GraphQL the error responses should be in second format.

https://www.apollographql.com/docs/apollo-server/data/errors/

Most helpful comment

You can pass a formatError when registering the GraphQL module such as:

      formatError: (error: GraphQLError) => {
        const graphQLFormattedError: GraphQLFormattedError = {
          message: error.extensions?.exception?.response?.message || error.message,
        };
        return graphQLFormattedError;
      }

I found the answer here: https://stackoverflow.com/a/64129469/5959721

All 11 comments

This is a show-stopper - every single thrown HTTP exception results in INTERNAL_SERVER_ERROR, making GraphQL unusable in nest.

Any updates?

This is a show-stopper - every single thrown HTTP exception results in INTERNAL_SERVER_ERROR, making GraphQL unusable in nest.

Any updates?

I found a workaround.

https://www.apollographql.com/docs/apollo-server/data/errors/

You can import these erros like:

import { UserInputError } from "apollo-server-express"

Then it will work as intended.

+1

The nestjs-graphql lib could re-export native apollo errors and then we can use formatError to re-throw correct graphql error.

//edit: Ok, it's possible to install apollo-server-errors an use these errors.

+1

You can pass a formatError when registering the GraphQL module such as:

      formatError: (error: GraphQLError) => {
        const graphQLFormattedError: GraphQLFormattedError = {
          message: error.extensions?.exception?.response?.message || error.message,
        };
        return graphQLFormattedError;
      }

I found the answer here: https://stackoverflow.com/a/64129469/5959721

So what is the possible fix for this currently? Is that we use apollo server errors instead of the BadRequestException of nestjs?

How do we handle with class validator errors as well? Is there a way to check the difference between both possible error types?

@leograde Thanks for the tip.

I added my own custom formatter that looks like this

formatError: (error: GraphQLError) => error.extensions?.exception?.response || || error.message,

Is there any benefit behind returning the GraphQLFormattedError rather than the custom structure?

Bump?

Another patch solution ...

  • catching validation errors with global ValidationPipe and transform to UserInputError from apollo-server-errors
  app.useGlobalPipes(
    new ValidationPipe({
      transform: true,
      exceptionFactory: (errors: ValidationError[]): => {
        return new UserInputError('VALIDATION_ERROR', {
          invalidArgs: errors,
        });
      },
    }),
  );
  • catching and formatting UserInputError to GraphQLFormattedError
    GraphQLModule.forRoot({
      autoSchemaFile: true,
      debug: false,
      formatError: (error: GraphQLError) => {
        if (error.message === 'VALIDATION_ERROR') {
          const extensions = {
            code: 'VALIDATION_ERROR',
            errors: [],
          };

          Object.keys(error.extensions.invalidArgs).forEach((key) => {
            const constraints = [];
            Object.keys(error.extensions.invalidArgs[key].constraints).forEach(
              (_key) => {
                constraints.push(
                  error.extensions.invalidArgs[key].constraints[_key],
                );
              },
            );

            extensions.errors.push({
              field: error.extensions.invalidArgs[key].property,
              errors: constraints,
            });
          });

          const graphQLFormattedError: GraphQLFormattedError = {
            message: 'VALIDATION_ERROR',
            extensions: extensions,
          };

          return graphQLFormattedError;
        } else {
          return error;
        }
      },
    }),

Thanks to @prokopsimek @leograde

Another patch solution ...

  • catching validation errors with global ValidationPipe and transform to UserInputError from apollo-server-errors
  app.useGlobalPipes(
    new ValidationPipe({
      transform: true,
      exceptionFactory: (errors: ValidationError[]): => {
        return new UserInputError('VALIDATION_ERROR', {
          invalidArgs: errors,
        });
      },
    }),
  );
  • catching and formatting UserInputError to GraphQLFormattedError
    GraphQLModule.forRoot({
      autoSchemaFile: true,
      debug: false,
      formatError: (error: GraphQLError) => {
        if (error.message === 'VALIDATION_ERROR') {
          const extensions = {
            code: 'VALIDATION_ERROR',
            errors: [],
          };

          Object.keys(error.extensions.invalidArgs).forEach((key) => {
            const constraints = [];
            Object.keys(error.extensions.invalidArgs[key].constraints).forEach(
              (_key) => {
                constraints.push(
                  error.extensions.invalidArgs[key].constraints[_key],
                );
              },
            );

            extensions.errors.push({
              field: error.extensions.invalidArgs[key].property,
              errors: constraints,
            });
          });

          const graphQLFormattedError: GraphQLFormattedError = {
            message: 'VALIDATION_ERROR',
            extensions: extensions,
          };

          return graphQLFormattedError;
        } else {
          return error;
        }
      },
    }),

Thanks to @prokopsimek @leograde

This worked for me thanks @dayota

Was this page helpful?
0 / 5 - 0 ratings