by Abi Noda

Custom error pages in React with GraphQL and Error Boundaries

GitHub’s awesome 500 error page

If you like this article please support me by checking out Pull Reminders, a Slack bot that sends your team automatic reminders for GitHub pull requests.

One challenge I recently ran into while working with GraphQL and React was how to handle errors. As developers, we’ve likely implemented default 500, 404, and 403 pages in server-rendered applications before, but figuring out how to do this with React and GraphQL is tricky.

In this post, I’ll talk about how our team approached this problem, the final solution we implemented, and interesting lessons from the GraphQL spec.

Background

The project I was working on was a fairly typical CRUD app built in React using GraphQL, Apollo Client, and express-graphQL. We wanted to handle certain types of errors — for example, the server being down — by displaying a standard error page to the user.

Our initial challenge was figuring out the best way to communicate errors to the client. GraphQL doesn’t use HTTP status codes like 500, 400, and 403. Instead, responses contain an errors array with a list of things that went wrong (read more about errors in the GraphQL spec).

For example, here’s what our GraphQL response looked like when something broke on the server:

Since GraphQL error responses return HTTP status code 200, the only way to identify the kind of error was to inspect the errors array. This seemed like a poor approach because the error message property contained the exception thrown on the server. The GraphQL spec states that the value of message is intended for developers, but it does not specify whether the value should be a human-readable message or something designed to be programmatically handled:

Every error must contain an entry with the key message with a string description of the error intended for the developer as a guide to understand and correct the error.

Adding Error Codes to GraphQL Responses

To solve for this, we added standardized error codes to our error objects, which could be used by clients to programmatically identify errors. This was inspired by how Stripe’s REST API returns string error codes in addition to human-readable messages.

We decided on three error codes to start: authentication_error, resource_not_found, and server_error.

To add these to our GraphQL responses, we passed our own formatError function to express-graphql that maps exceptions thrown on the server to standard codes which get added to the response. The GraphQL spec generally discourages adding properties to error objects, but does allow for it by nesting those entries in a extensions object.

Our GraphQL response errors were then easy to classify:

While we developed our own way of adding codes to responses generated by express-graphql, apollo-server appears to offer similar built-in behavior.

Rendering error pages with React Error Boundaries

Once we figured out a good way of handling errors in our server, we turned our attention to the client.

By default, we wanted our app to display a global error page (for example, a page with the message “oops something went wrong”) whenever we encountered a server_error, authorization_error, or authorization_not_found. However, we also wanted the flexibility to be able to handle an error in a specific component if we wanted to.

For example, if a user was typing something into a search bar and something went wrong, we wanted to display an error message in-context, rather than flash over to an error page.

To achieve this, we first created a component called GraphqlErrorHandler that would sit between apollo-client’s Query and Mutation components and their children to be rendered out. This component checked for error codes in the response threw an exception if it identified a code we cared about:

To use the GraphqlErrorHandler, we wrapped apollo-client’s Query and Mutation components:

Our feature component then used our own Query component instead of directly accessing react-apollo:

Now that our React app was throwing exceptions when the server returned errors, we wanted to handle these exceptions and map them to the appropriate behavior.

Remember from earlier that our goal was to default to displaying global error pages (for example, a page with the message “oops something went wrong”), but still have the flexibility to handle an error locally within any component if we desired.

React error boundaries provide a fantastic way of doing this. Error boundaries are React components that can catch JavaScript errors anywhere in their child component tree so you can handle them with custom behavior.

We created an error boundary called GraphqlErrorBoundary that would catch any server-related exceptions and display the appropriate error page:

We use the error boundary as a wrapper for all of our app’s components:

Error boundaries can be used deeper in the component tree if we want to handle errors in a component instead of rendering an error page.

For example, here’s what it’d look if we wanted custom error handling behavior in our component from earlier:

Wrap-up

GraphQL is still relatively new, and error handling is a common challenge that developers seem to be running into. By using standardized error codes in our GraphQL responses, we can communicate errors to clients in a useful and intuitive way. In our React apps, error boundaries provide a great way to standardize our app’s error handling behavior while still having flexibility when we need it.