9

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?

Le garcon
  • 3,929
  • 5
  • 23
  • 38
  • why don't you customize your response?? you can catch the error before sending it to client and then create a customized response, like an array containing added members – Mo Ganji Oct 12 '18 at 11:04

3 Answers3

7

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.

Daniel Rearden
  • 58,313
  • 8
  • 105
  • 113
  • With Apollo, does the server need to set some option, in order to return partial errors? I have a field resolver that may return "Unauthorized", but when an unauthorized client requests the type, the whole query blows up, and the response has `data: null` (not just for that field). The other fields don't require authorization, but are not returned. – Dan Dascalescu May 02 '20 at 13:01
  • 1
    @DanDascalescu Sounds like your field is non-nullable. If an execution error is thrown inside a field's resolver, that field will resolve to null... but if the field is non-nullable, its parent will resolve to null instead since non-nullable field's can't be null. If the parent field is also non-nullable, then it's parent will resolve to null... and so on, until we hit `data`. See [here](http://spec.graphql.org/June2018/#sec-Errors-and-Non-Nullability) in the spec. – Daniel Rearden May 02 '20 at 14:00
3

If you think about combining the GraphQL error - there is a way to do it in Apollo. You need to set errorPolicy to all. That will help you notify users about the error and at the same time have as much data as possible.

none: This is the default policy to match how Apollo Client 1.0 worked. Any GraphQL Errors are treated the same as network errors and any data is ignored from the response.

ignore: Ignore allows you to read any data that is returned alongside GraphQL Errors, but doesn’t save the errors or report them to your UI.

all: Using the all policy is the best way to notify your users of potential issues while still showing as much data as possible from your server. It saves both data and errors into the Apollo Cache so your UI can use them.

But according to best practices, you shouldn't manipulate it in this way. This is a great article about handling errors in GraphQL.

So, preferable way is to add "errors" field as part of your response and handle it in JS code.

Dan Dascalescu
  • 110,650
  • 40
  • 276
  • 363
Yevhenii Herasymchuk
  • 1,700
  • 11
  • 20
1

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!
}

type ErrorType {
  id: ID!
  message: String!
  statusCode: Int!
}

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

Response:

addMembersToTeam(membersIds: [ID!]! teamId: ID!): {
...on MemberType{
  id,
  name,
}
...on ErrorType{
  id,
  message,
  statusCode,
}
}
Jha Nitesh
  • 118
  • 1
  • 8