Banner Default Image
Back-to-blogs

200 OK! Error Handling in GraphQL

Posted-on January 2020 By Sasha Solomon

Blog Img

Sasha Solomon is a software engineer at Twitter working on the Core API Team helping build the next generation API with GraphQL and Scala.

With such a complex language, there are many slip-ups that you can face using GraphQL- Sasha’s excellent advice tells you how to effectively handle these model "errors".

We all know how great GraphQL is when things go well, but what happens when things don’t go well?

How do we handle errors in GraphQL? How can we do it in a way that’s easy to understand?

Let’s start by running a simple GraphQL query:

We might get something like this:

This is what we hope to see. But it’s not always smooth sailing. What happens when we make this same query but something goes wrong?

You get an error. And you’ll find it in the errors array. Canonically, this is where errors can be found in a GraphQL response.

There’s a lot to unpack here. We can see that there was an error that happened when we queried user. We can see that the error message was “Object not found”, and where it was located in our query.

The problem

So, what’s wrong with this?

  • All errors are treated the same

All errors end up in the errors array, no matter what kind of error they are.

  • It’s hard to know where the error came from

Our example was a simple query, but in more complex queries it’s harder to know where the error came from, especially if it’s, say, a list of items.

  • It’s hard for the client to know what errors to care about

Clients get all errors in the errors array, so clients can’t even query for errors. This means clients don’t know what cases there even are to handle, let alone which ones are important, which ones we can ignore, etc.

And, through all of this, the the response doesn’t make it easy for the client to display something useful to the user.

What is an error?

Before we really dig into this, we should really understand what an error is.

When we think of an “error”, we think of things like Internal Server Error, Deleted User, Bad Gateway, Unavailable in Country, Suspended User, etc.

But, it seems like all of these “errors” aren’t equivalent.

An Internal Server Error doesn’t seem at all the same as a Suspended User.

The same is true for a Bad Gateway versus Unavailable in Country. It seems like these are different types of errors.

Error Categories

When we start thinking of “errors” in this way, we can separate them into categories.

When we do this, we can see with things like Internal Server Error and Bad Gateway, something went wrong in servicing the request. So, these are definitely errors.

But in cases like Deleted User, Unavailable in Country, and Suspended User, nothing went wrong at all. They aren’t the result we expect most of the time, but they certainly aren’t errors. They’re results.

But let’s walk through why.

Errors

If we make a request to our GraphQL server, our server will make subsequent calls to our backends/services. If one of our services throws an exception, we’ll get a HTTP 500 (or equivalent) back, and something like “Server Error” will end up in our errors array.

So, if we get an error, that means we couldn’t get the data we asked for, for some reason.

Alternative Results

For “alternative results”, this looks a little different. We have our client and our client hits the/graphql endpoint. When our graphql server calls out to our services, our services might send something back like Unavailable in Country or Suspended User.

But these aren’t really errors…we got the data we requested. So really, our “alternative results” are just results.

For example, if we request a User we expect varying states of a User back. A User could be a normal User, but isn’t a Suspended User a User? And a Blocked User ? These are just different states of a User. So shouldn’t we be able to query for these if we care about them?

If we’ve learned anything about GraphQL it’s that you can query for what you care about.

Modelling Results in the GraphQL Schema

So how would we model this?

Say we have a User. We also have other results for what a User could be. These are all the possible results we have when we query for a User during normal operation.

We can use a Union (perfect for representing which of several states!) to create a UserResult that encompasses all of these possible results.

In modelling this way, we can also customise what each result looks like depending on what ResultType we have.

For example, if we have a Deleted User, we could include a Message, or if our user is Suspended, we could include a Policy Violation Link.

In GraphQL SDL, it might look something like this:

Which allows us to create a union type like this:

Clients only need to query for data they can use, so if they can’t handle a suspended user, they don’t query for it, and should also have some fallback behavior or a default case.

Now that these different states are in the schema (instead of implicit in an error), clients can easily see what cases exist to handle!

So when we make a query:

We get this as a result:

As we can see, we got a normal User response, and we know this is a User because of the __typename field (client libraries make this even easier to handle!).

But let’s say that this user is actually not available. If we run that same query again, we get this as a result:

We get an IsBlocked result instead, and all of the field we queried for on an IsBlocked response.

This is great because it means that:

  • Results are customisation for each entity

A User will have different Results than other types, for example Tweet, will; we can add different Results and customise fields for each type.

  • We know where the error came from

We know exactly where our error comes from in the query (because it’s attached to the entity); it’s actually encoded in the schema.

  • The client decides what errors it cares about and what errors it can ignore

The client can query or not query for different Results, so it decides what’s important.

Complex Schema Structures

If we use this way of modelling our Results, we can imagine using Results for different entities within the same query.

So in addition to User and the results we defined previously, we can also define the following:

Given this, we can define our new Result Type:

Putting this all together, we can now make a GraphQL query that includes both of the Result Types we defined:

We might get something like this:

Or, if the image we queried for is Copyrighted, we get something like this back instead:

In addition to having Results that are customisable for each entity, knowing where the error came from, and letting the client decide what errors it cares about and which it can ignore, this also means that

  • “Errors” don’t cause failures for nested queries

We were able to query for two different ResultTypes, and when one “failed”, it didn’t cascade and cause the entire query to fail.

  • We can tune how verbose we want results to be

We can add result types to any entity we want (like we did with User and ProfileImage) or NOT! We get to decide. New result types get added to the schema explicitly instead of sneaking in silently as new kinds of errors.

  • We can more accurately represent our data

Our schema structure mirrors what our data looks like, which makes it easier to reason about.

And the nice thing is, you can customise this as much as you need to.

Results

Some things to take away when thinking about modelling “errors” in GraphQL, and just modelling in general:

  • Not everything is an error

  • Model your errors as errors, model your actual results as results

  • Results are usually things you want to display

GraphQL intentionally does not prescribe how to model your schema, and the same goes for errors and results. It’s up to you to decide what describes your data best.

This is certainly not where modelling “errors” or results in the schema ends. How do we handle rate limiting? What about authentication or authorisation errors? Do these things belong in the schema or not?

Sometimes there’s a clear answer, and sometimes there’s not! How you use your data determines how you should model it.

If you’d like to learn about all of this via video instead of a Medium post, you can watch it here, here, or here!'

This article was written by Sasha Solomon and published on Medium.com.

HiTalent

Arrange a

Callback

Drop us your Email and we'll arrange a call to discuss how we can help