Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

How to return both error and data in a graphql resolver?

I was thinking about ways of implementing graphql response that would contain both an error and data.

Is it possible to do so without creating a type that would contain error?

e.g.

Mutation addMembersToTeam(membersIds: [ID!]! teamId: ID!): [Member] adds members to some team. Suppose this mutation is called with the following membersIds: [1, 2, 3].

Members with ids 1 and 2 are already in the team, so an error must be thrown that these members cannot be added, but member with an id 3 should be added as he is not in the team.

I was thinking about using formatResponse but seems that I can't get an error there.

Is it possible to solve this problem without adding error field to the return type?

like image 707
Le garcon Avatar asked Oct 12 '18 10:10

Le garcon


People also ask

How do you return an error in GraphQL?

To add errors to your data, you need to use the Union type (a.k.a. Result ) in your GraphQL schema. Also, the logic in the resolver is rewritten so that next to the result or error for the operation you also need to return a type for it.

How do you handle exceptions in GraphQL?

Any GraphQL server will automatically handle syntactical and validation errors and inform the client appropriately, but the exceptions encountered in the resolver functions usually require application-specific handling. With your current stack, error handling can be customized on a few different levels.

Does GraphQL return status code?

A GraphQL API will always return a 200 OK Status Code, even in case of error.

How does resolver work in GraphQL?

Each field on each type is backed by a function called the resolver which is provided by the GraphQL server developer. When a field is executed, the corresponding resolver is called to produce the next value. If a field produces a scalar value like a string or number, then the execution completes.


2 Answers

Is it possible to solve this problem without adding error field to the return type?

Unfortunately, no.

A resolver can either return data, or return null and throw an error. It cannot do both. To clarify, it is possible to get a partial response and some errors. A simple example:

const typeDefs = `
  type Query {
    foo: Foo
  }

  type Foo {
    a: String
    b: String
  }
`
const resolvers = {
  Query: {
    foo: () => {},
  }
  Foo: {
    a: () => 'A',
    b: () => new Error('Oops!'),
  }
}

In this example, querying both fields on foo will result in the following response:

{
  "data": {
    "foo": {
      "a": "A",
      "b": null
    }
  },
  "errors": [
    {
      "message": "Oops",
      "locations": [
        {
          "line": 6,
          "column": 5
        }
      ],
      "path": [
        "foo",
        "b"
      ]
    }
  ]
}

In this way, it's possible to send back both data and errors. But you cannot do so for the same field, like in your question. There's a couple of ways around this. As you point out, you could return the errors as part of the response, which is usually how this is done. You could then use formatResponse, walk the resulting data, extract any errors and combine them with them with any other GraphQL errors. Not optimal, but it may get you the behavior you're looking for.

Another alternative is to modify the mutation so it takes a single memberId. You can then request a separate mutation for each id you're adding:

add1: addMemberToTeam(memberId: $memberId1 teamId: $teamId): {
  id
}
add2: addMemberToTeam(memberId: $memberId2 teamId: $teamId): {
  id
}
add3: addMemberToTeam(memberId: $memberId3 teamId: $teamId): {
  id
}

This can be trickier to handle client-side, and is of course less efficient, but again might get you the expected behavior.

like image 70
Daniel Rearden Avatar answered Sep 19 '22 18:09

Daniel Rearden


We can achieve this by using a union. I would recommend visiting the great article Handling GraphQL errors like a champ

Example:

Mutation part: We can return the union type for the response & capture the result according to types.

type MemberType {
  id: ID!
  name: String!
}

enum ErrorType {
  BAD_REQUEST_ERROR
  FORBIDDEN_ERROR
  INTERNAL_SERVER_ERROR
  NOT_FOUND_ERROR
  UNAUTHORIZED_ERROR
}

type GraphqlError {
  type: ErrorType!
  code: String!
  message: String!
  helpLink: URL
}


union UserRegisterResult = MemberType | GraphqlError;
addMembersToTeam(membersIds: [ID!]! teamId: ID!): UserRegisterResult!

Response:

addMembersToTeam(membersIds: [ID!]! teamId: ID!): {
...on MemberType{
  id,
  name,
}
...on GraphqlError{
  id,
  message,
  statusCode,
}
}
like image 45
Jha Nitesh Avatar answered Sep 22 '22 18:09

Jha Nitesh