saeta
saeta

Reputation: 4238

GraphQL - How to respond with different status code?

I'm having a trouble with Graphql and Apollo Client.

I always created different responses like 401 code when using REST but here I don't know how to do a similar behavior.

When I get the response, I want it to go to the catch function. An example of my front-end code:

client.query({
  query: gql`
    query TodoApp {
      todos {
        id
        text
        completed
      }
    }
  `,
})
  .then(data => console.log(data))
  .catch(error => console.error(error));

Can anybody help me?

Upvotes: 28

Views: 46507

Answers (5)

Glenn Sonna
Glenn Sonna

Reputation: 1931

There has been a recent addition to the spec concerning errors outputs:

GraphQL services may provide an additional entry to errors with key extensions. This entry, if set, must have a map as its value. This entry is reserved for implementors to add additional information to errors however they see fit, and there are no additional restrictions on its contents.

Now using the extensions field you can custom machine-readable information to your errors entries:

{
  "errors": [
    {
      "message": "Name for character with ID 1002 could not be fetched.",
      "locations": [ { "line": 6, "column": 7 } ],
      "path": [ "hero", "heroFriends", 1, "name" ],
      "extensions": {
        "code": "CAN_NOT_FETCH_BY_ID",
        "timestamp": "Fri Feb 9 14:33:09 UTC 2018"
      }
    }
  ]
}

Latest version of Apollo-Server is spec-compliant with this feature check it out, Error Handling.

Upvotes: 11

I think a missing concern in the discussion about graphql and errors, are errors in the transformation from http to gql, and this is oftwn where a 401 should occour.

When transforming the request, you should transform the Authorization header (or whatever authentication method you are using) to a user and if it cannot be authenticated, it should return a HTTP 401 error - this is not a part of the graph or the spec for you api, and is just a matter of whether the user can be validated or not. You don't even have to inspect the query.

On the other hand a 403 error will most likely occour in the gql layer (and will probably not use the http status code, but that is another discussion), since it can be very domain specific and you have to know the query to decide whether it is forbidden or not.

A HTTP 403 status could be used to tell the user that he cannot access the gql api at all.

We solve this issue in express/nestjs by having a middleware before hitting the graphql layer that enriches the request with the user (maybe undefined) or fails if the user cannot be authenticated. I don't think 401 should ever be returned if you don't provide credentials (or the like).

Upvotes: 0

dipole_moment
dipole_moment

Reputation: 5854

After experimenting with this for a bit, I realized that some important details are missing. Mainly, if you have a custom error object with custom fields, the above examples will allow you to read your custom properties because it appears that custom errors are casted into a standard Error object with only a message property.

Here is what my formatError function looks like (note the originalError property):

  app.use('/graphql', auth.verifyAccess, graphqlHTTP((req, res) => {
    return {
      schema: makeExecutableSchema({
        typeDefs: typeDefs,
        resolvers: rootResolver
      }),
      graphiql: true,
      formatError: (err) => ({
        message: err.originalError.message || err.message,
        code: err.originalError.code || 500
      }),
    }
  }));

The originalError prop seems to always be set but as a safeguard you could use lodash get property.

And I have a defined custom error class called APIError

class APIError extends Error {
  constructor({ code, message }) {
    const fullMsg = `${code}: ${message}`;

    super(fullMsg);
    this.code = code;
    this.message = message;
  }
}

export default APIError;

In my resolvers, I throw exceptions as such:

  const e = new APIError({
    code: 500,
    message: 'Internal server error'
  });

Upvotes: 1

David Revelo
David Revelo

Reputation: 86

Just to complement Glenn's answer, here is the part of Graphql Spec that defines how errors should be handled. So to know if the request failed (or partially failed) your can check for the "errors" key at the root of the response.

Upvotes: 2

helfer
helfer

Reputation: 7172

The way to return errors in GraphQL (at least in graphql-js) is to throw errors inside the resolve functions. Because HTTP status codes are specific to the HTTP transport and GraphQL doesn't care about the transport, there's no way for you to set the status code there. What you can do instead is throw a specific error inside your resolve function:

age: (person, args) => {
  try {
    return fetchAge(person.id);
  } catch (e) {
    throw new Error("Could not connect to age service");
  }
}

GraphQL errors get sent to the client in the response like so:

{
  "data": {
    "name": "John",
    "age": null
  },
  "errors": [
    { "message": "Could not connect to age service" }
  ]
}

If the message is not enough information, you could create a special error class for your GraphQL server which includes a status code. To make sure that status code gets included in your response, you'll have to specify the formatError function when creating the middleware:

app.use('/graphql', bodyParser.json(), graphqlExpress({ 
    schema: myGraphQLSchema,
    formatError: (err) => ({ message: err.message, status: err.status }),
}));

Upvotes: 40

Related Questions