Handling GraphQL Errors with Hasura & React
Table of contents
- Introduction
- Errors: REST Vs GraphQL
- An introduction to GraphQL Errors
- Custom error handling with React
- GraphQL Errors
- Error policies (apollo client)
- Summary
- A quick introduction to common errors experienced in GraphQL APIs
- How to handle GraphQL errors while building APIs with Hasura
- Building custom error pages on a client side React app

"errors": [{
"message": "Name for character with ID 1002 could not be fetched.",
"locations": [ { "line": 6, "column": 7 } ],
"path": [ "hero", "heroFriends", 1, "name" ]
}]
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 implementers to add additional information to errors however they see fit, and there are no additional restrictions on its contents. (docs).
“errors”: [{
“extensions”: {
“path”: “$.selectionSet.post.selectionSet.name”,
“code”: “validation-failed”
},
“message”: “field \”name\” not found in type: ‘post’”
}]
- Server errors: These include errors like 5xx HTTP codes and 1xxx WebSocket codes. Whenever server error occurs, server is generally aware that it is on error or is incapable of performing the request. Server errors may also occur due to closing of websocket connection between client and server, which may happen due to various reasons (see CloseEvent for different types of 1xxx error codes). No data is returned in this case as GraphQL endpoint is not reached.
- Client errors: These include errors like malformed headers sent by client, unauthorized client, request timeout, rate-limited api, request resource deleted, etc. All the client errors return 4xx HTTP codes. Same with server errors, no data is returned.
- Error in parse/validation phase of query: These include errors while parsing the GraphQL query. For example, if client sends malformed GraphQL request, i.e. syntax error. Or if the query does not pass GraphQL internal validation, i.e. client sent inputs that failed GraphQL type checking. In both these cases, no partial data can be returned. In case of validation error,
errors
array is returned showing what went wrong, while queries with syntax errors are usually not sent to GraphQL endpoint and are caught at the client side. - Errors thrown within the resolvers: Resolver errors may occur due to lots of reasons, depending on the implementation of resolver functions. For example, errors like poorly written database queries, or errors thrown on purpose like restricting users from particular countries to access some data. Most importantly, these kind of errors can return partial data/fields that are resolved successfully alongside an error message.
Special case GraphQL errors with Hasura
404 resource not found error
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
import { Switch, Route } from "react-router-dom"; | |
import PostList from "./PostList"; | |
import NotFound from "./NotFound"; | |
function App() { | |
return ( | |
<Switch> | |
<Route exact path="/" component={PostList} /> | |
<Route path="*" component={NotFound} /> | |
</Switch> | |
); | |
} |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
import React from "react"; | |
import { Link } from "react-router-dom"; | |
import resourceNotFound from "../styles/404.png"; | |
const NotFound = () => ( | |
<div> | |
<img | |
src={resourceNotFound} | |
style={{ | |
width: "85%", | |
height: "auto", | |
display: "block", | |
margin: "auto", | |
position: "relative" | |
}} | |
/> | |
<center> | |
<Link to="/">Return to Home Page</Link> | |
</center> | |
</div> | |
); | |
export default NotFound; |

Network errors / Server errors
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
import { onError } from "apollo-link-error"; | |
import {NetworkError} from "./NetworkError"; | |
. | |
. | |
const httpLink = new HttpLink({ | |
// uri: "https://hackernews-clone-2.herokuapp.com/v1/graphql" | |
uri: "https://hackernews-clone-2.herokuapp.com/v1/" // change uri intentionally to cause network-error | |
}); | |
const errorLink = onError(({ graphQLErrors, networkError }) => { | |
if (graphQLErrors) | |
graphQLErrors.map(({ message, extensions }) => { | |
console.log( | |
`[GraphQL error]: Message: ${message}, Location: ${extensions.code}` | |
); | |
}); | |
if (networkError) { | |
console.log(`[Network error]: ${networkError}`); | |
props.history.push('/network-error') // redirect to network-error route | |
} | |
}); | |
const client = new ApolloClient({ | |
link: errorLink.concat(authLink.concat(httpLink)), // concat errorLink to authlink and httplink, errorLink will catch errors | |
cache: new InMemoryCache() | |
}); | |
return ( | |
<Switch> | |
<Route exact path="/" component={PostList} /> | |
<Route path ="/network-error" component={NetworkError} /> | |
<Route path="*" component={NotFound} /> | |
</Switch> | |
); |
See this thread to better understandreact-router
andhistory
object. Note that we are usingwithRouter
inApp.js
to get access to thehistory
object throughprops
.
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
import React from "react"; | |
import { Link } from "react-router-dom"; | |
import networkError from "../styles/network-error.jpg"; | |
const NetworkError = () => ( | |
<div> | |
<img | |
src={networkError} | |
style={{ | |
width: "85%", | |
height: "500px", | |
display: "block", | |
margin: "auto", | |
position: "relative" | |
}} | |
/> | |
<center> | |
<Link to="/">Refresh</Link> | |
</center> | |
</div> | |
); | |
export default NetworkError; |

Hasura GraphQL API
Handling errors at top level
{
“errors”: [{
“extensions”: {
“path”: “$.selectionSet.dogs.selectionSet.name”,
“code”: “validation-failed”
},
“message”: “field \”name\” not found in type: ‘dogs’”
}]
}{
"errors": [{
"extensions": {
"path": "$.selectionSet.insert_dogs.args.objects",
"code": "data-exception"
},
"message": "invalid input syntax for integer: \"a\""
}]
}
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
import SomethingWentWrong from "./SomethingWentWrong"; | |
. | |
. | |
const errorLink = onError( | |
({ graphQLErrors, networkError, operation, forward }) => { | |
if (graphQLErrors) | |
graphQLErrors.map(({ message, extensions }) => { | |
switch (extensions.code) { | |
case "data-exception": | |
case "validation-failed": | |
props.history.push("/something-went-wrong"); // redirect to something-went-wrong page | |
break; | |
default: | |
// default case | |
console.log(extensions.code); | |
} | |
}); | |
if (networkError) { | |
console.log(`[Network error]: ${networkError}`); | |
props.history.push("/network-error"); | |
} | |
} | |
); | |
return ( | |
<Switch> | |
<Route exact path="/" component={PostList} /> | |
<Route path ="/network-error" component={NetworkError} /> | |
<Route path="/something-went-wrong" component={SomethingWentWrong} /> | |
<Route path="*" component={NotFound} /> | |
</Switch> | |
); |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
import React from "react"; | |
import { Link } from "react-router-dom"; | |
import somethingWentWrong from "../styles/something-went-wrong.png"; | |
const SomethingWentWrong = () => ( | |
<div> | |
<img | |
src={somethingWentWrong} | |
style={{ | |
width: "85%", | |
height: "600px", | |
display: "block", | |
margin: "auto", | |
position: "relative" | |
}} | |
/> | |
<center> | |
<Link to="/">Refresh</Link> | |
</center> | |
</div> | |
); | |
export default SomethingWentWrong; |

Custom logic on certain errors
{
"errors": [{
"extensions": {
"path": "$",
"code": "invalid-jwt"
},
"message": "Could not verify JWT: JWSError (JSONDecodeError \"protected header contains invalid JSON\")"
}]
}{
"errors": [{
"extensions": {
"path": "$",
"code": "invalid-jwt"
},
"message": "Could not verify JWT: JWTExpired"
}]
}
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
const errorLink = onError( | |
({ graphQLErrors, networkError, operation, forward }) => { | |
if (graphQLErrors) | |
graphQLErrors.map(({ message, extensions }) => { | |
switch (extensions.code) { | |
case "invalid-jwt": | |
// refetch the jwt | |
const oldHeaders = operation.getContext().headers; | |
const token = getAccessToken(); | |
operation.setContext({ | |
headers: { | |
...oldHeaders, | |
authorization: `Bearer ${token}` | |
} | |
}); | |
// retry the request, returning the new observable | |
return forward(operation); | |
break; | |
default: | |
// default case | |
console.log(extensions.code); | |
} | |
}); | |
if (networkError) { | |
console.log(`[Network error]: ${networkError}`); | |
props.history.push("/network-error"); | |
} | |
} | |
); |

Handling errors at component level
{
“errors”:[{
“extensions”: {
“path”:”$.selectionSet.insert_point.args.objects”,
”code”:”constraint-violation”
},
”message”:”Uniqueness violation. duplicate key value violates unique constraint \”point_user_id_post_id_key\””
}]
}
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
const [upvotePost, { error, loading, data }] = useMutation(UPVOTE_POST, { | |
variables: { postId: props.post.id, userId: loggedUserId }, | |
refetchQueries: [{ query: POSTS_LIST }] | |
}); | |
if (error) { | |
error.graphQLErrors.map(error => { | |
console.log(error); | |
if (error.extensions.code === "constraint-violation") | |
toast(`${error.message}`); | |
}); | |
} |

const { loading, error, data } = useQuery(MY_QUERY, { errorPolicy: 'all' });
References
- The Definitive Guide to Handling GraphQL Errors
- Full Stack Error Handling with GraphQL and Apollo
- Error handling
New to Hasura? Getting started is easy – simply set up a project on Hasura Cloud and follow documentation to unlock your data's true potential.
Related reading