Calvin Flegal
Calvin Flegal

Reputation: 315

Why aren't NestJS GraphQL validation errors placed in the error message field?

I'm seeing some class-validator errors in an unexpected location. I would expect a more convenient format for dealing with errors, but perhaps I'm unaware of some graphQL tricks for dealing with objects in the extensions field...

While running NestJS sample/23-graphql-code-first, I see the following in the GraphQL playground:

With input:

  addRecipe(newRecipeData: {
    description: "too short"
    title: "this field should fail for being too long"
    ingredients: ["normal"]

  }) {
    title
  }
}

I am returned:

  "errors": [
    {
      "message": "Bad Request Exception",
      "locations": [
        {
          "line": 2,
          "column": 3
        }
      ],
      "path": [
        "addRecipe"
      ],
      "extensions": {
        "code": "INTERNAL_SERVER_ERROR",
        "exception": {
          "response": {
            "statusCode": 400,
            "message": [
              "title must be shorter than or equal to 30 characters",
              "description must be longer than or equal to 30 characters"
            ],
            "error": "Bad Request"
          },
          "status": 400,
          "message": "Bad Request Exception",
          "stacktrace": [
            "Error: Bad Request Exception",
            "    at ValidationPipe.exceptionFactory nest/sample/23-graphql-code-first/node_modules/@nestjs/common/pipes/validation.pipe.js:78:20)",
          ...
          ]
        }
      }
    }
  ],
  "data": null
}

These errors are deeply nested, and "Bad Request Exception" is not so useful. Is this working as intended?

Upvotes: 15

Views: 18200

Answers (6)

AouledIssa
AouledIssa

Reputation: 2824

If you are looking for how to strip the stack trace off of the GraphQL Error you just have to make debug field as false

GraphQLModule.forRoot<ApolloDriverConfig>({
      driver: ApolloDriver,
      debug: false, // set as ENV param
      playground: true, // set as ENV param
      autoSchemaFile: join(process.cwd(), 'src/schema.gql'),
    })

If you set both debug and playground as ENV params to true for dev environment, you will not see the stack trace in prod.

Upvotes: -2

Aymen Jarouih
Aymen Jarouih

Reputation: 514

Here is my solution you could add error and ok objects to your @ObjectType.

@ObjectType()
export class UserCreateOutput {
    //can be null if error
    @Field(() => User, { nullable: true })
    user?: User;
    @Field(() => Boolean)
    ok?: boolean;
    @Field(() => String, { nullable: true })
    error?: string;
}

On .service.ts you could catch and show error as a return.

catch (error) { return { ok: false, error: 'Could not create account', //Your custom error message user: null, //Make sure that this object can be null :) };

Upvotes: -3

Toan
Toan

Reputation: 361

If you're using mercurius, you can use this:

// In main.ts file, just register ValidationPipe like normal
app.useGlobalPipes(new ValidationPipe({ forbidUnknownValues: true }))
// In app.module.ts file, add this errorFormatter:
GraphQLModule.forRoot<MercuriusDriverConfig>({
  ...
  errorFormatter: execution => {
    const [error] = execution.errors // take first error
    const originalError = error?.originalError
    if (originalError instanceof HttpException)
      return {
        statusCode: originalError.getStatus(),
        response: { data: originalError.getResponse() as any }
      }
    return { statusCode: 500, response: execution }
  }
})

Upvotes: 8

Leonardo Gomes
Leonardo Gomes

Reputation: 335

The best solution is based on this official comment: creates your own formatError or formatResponse function.

I just used in this project on GitHub and it worked fine!

The change needs to be added to src/app.module.ts, by default.

My Sample:

    import { GraphQLError, GraphQLFormattedError } from 'graphql';
    
    GraphQLModule.forRoot({
      autoSchemaFile: true,
      debug: false,
      formatError: (error: GraphQLError) => {
        const graphQLFormattedError: GraphQLFormattedError = {
          message: error?.extensions?.exception?.response?.message || error?.message,
        };
        return graphQLFormattedError;
      },
    }),

And now, I'm getting a formatted error response from GraphQL:

{
  "errors": [
    {
      "message": [
        "firstName must be longer than or equal to 1 characters",
        "lastName must be longer than or equal to 1 characters"
      ]
    }
  ],
  "data": null
}

Upvotes: 27

Fabio Espinosa
Fabio Espinosa

Reputation: 1012

I ended up doing this in main.ts (although i'm sure there is a better solution)

  app.useGlobalPipes(
    new ValidationPipe({
      exceptionFactory: (errors: ValidationError[]) => {
        const error_messages = errors.map(error =>
          Object.values(error.constraints),
        );
        return new BadRequestException(error_messages.toString());
      },
      forbidUnknownValues: false,
    }),
  );

Upvotes: 2

Jo&#227;o Silva
Jo&#227;o Silva

Reputation: 167

When you use Validation pipe you can change that behaviour:

app.useGlobalPipes(
    new ValidationPipe({
      exceptionFactory: errors => new BadRequestException(errors), // TODO: Use graphql errors instead
      forbidUnknownValues: true,
    }),
  );

Upvotes: 1

Related Questions