Catch All Errors With Apollo GraphQL (Client)

Catch All Errors With Apollo GraphQL (Client)

GraphQL makes is dead easy when handling errors. We're going to look at ways in which we would handle errors in a React application using GraphQL.

..its best to throw exceptions at the point where the errors occur. You do this because it's the point where you know the most about why the exception was triggered. – Michael Shaw

Let's install GraphQL with Apollo Client

npm install @apollo/client graphql
// index.js

import { ApolloClient, InMemoryCache, HttpLink } from '@apollo/client';
import { onError } from '@apollo/client/link/error';

// Logout could also be a Login page.
// Your action to take when a request is
// Unauthorized or the user is not
// authenticated with your server.
import { logout } from './utilities';

// Your API url for your GraphQL server where
// uri is the http(s) url.
const httpLink = new HttpLink({ uri: '/graphql' });

const logoutLink = onError(({ networkError }) => {
  if (networkError.statusCode === 401) {
  	// What to do when the user is not
    // authenticated?
    logout();
    // Or login()?
  }
})

const client = new ApolloClient({
  cache: new InMemoryCache(),
  link: logoutLink.concat(httpLink),
});

When working with API servers, be specific of the error code you return. See all available list of error codes here.

In general, only one error code should be listed above which will typically be the 401. Not limited to 401, but the action we want to trigger is a log out action.

We import a logout function which gets called whenever the response error code is 401. This function should undo what your login function does then presents a login screen/page for the user to log back into your application.

Handling component error

A typical React functional component would look something like this

import React from 'react';
import TodoItem from './TodoItem';
import { useQuery, gql } from '@apollo/client';

const GET_TODOS = gql`
    query GetTodos {
        todos {..}
    }
`;

export default function Todo() {
    const { data, loading, error } = useQuery(GET_TODOS);

    if (error) return <div>Error</div>;
    
    if (loading) return <div>Loading...</div>;

    return (
        <div>
            <h1>Todos</h1>
            {data.todos?.map(todo => <TodoItem key={todo.id} todo={todo} />)}
        </div>
    );
}

On page load we fetch our list of todos, from an external api server, then display the todos if no error. If any error, we show a div with the text "Error". To be specific about the type of error we want to present the user, we would need to change things up a little bit.

import React from 'react';
// Not described here:
import TodoItem from './TodoItem';
import { useQuery, gql } from '@apollo/client';

const GET_TODOS = gql`
    query GetTodos {
        todos {..}
    }
`;

/**
 * @see apollographql.com/docs/react/data/error-handling/#setting-an-error-policy
 * @param graphQLErrors 
 * @returns 
 */
const onError = (graphQLErrors) => {
    return (
        <ul>
           {graphQLErrors.map(({ message }, i) => (
                <li key={i}>{message}</li>
            ))}
        </ul>
    )
}


export default function Todo() {
    const { data, loading, error } = useQuery(GET_TODOS, { errorPolicy: 'all' });

    if (error) return onError(error.graphQLErrors);

    if (loading) return <div>Loading...</div>;

    return (
        <div>
            <h1>Todos</h1>
            {data.todos?.map(todo => <TodoItem key={todo.id} todo={todo} />)}
        </div>
    );
}

Depending on your use-case, you could display the error inline with the resolved field as shown here.