Error Handling in GraphQL With "Errors as Data"


Error handling in GraphQL is a topic that's often discussed in the GraphQL community. It's very different from REST, where you can rely on HTTP status codes to handle errors. In GraphQL, there are various ways to handle errors, and it's up to the implementor to decide which strategy to use. This blog post will look at the different ways to handle errors in GraphQL and how to use the "errors as data" approach as an alternative.
Errors object
The errors
field in a GraphQL response is a list of errors that occurred during the execution of the request. Depending on what library or framework you use to create your GraphQL API, this errors
object can contain different sets of information.
The GraphQL specification makes a distinction between two types of errors:
-
Request errors occur before the GraphQL engine has started executing the request. For example, if the request is not following valid GraphQL syntax, the request is missing required fields, or the operation you're trying to run doesn't exist in the schema, a request error is returned.
-
Field errors are thrown by the GraphQL engine when it's executing the request. For example, if the request is valid but the server cannot find the data in the data source or the data source is not available, a field error is returned.
Request errors are the same for every GraphQL implementation, so this blog post will focus on the field errors.
Field errors
Look at the snippet below that comes from a GraphQL schema. It shows a mutation called auth
that returns a non-nullable type called Token
. The Token
type has a field called token
, a non-nullable string.
type Token {
id: ID!
token: String!
}
type Mutation {
auth(email: String!, password: String!): Token!
}
Let's say you're trying to authenticate a user using the following mutation that passes in the value demo
for the email address and the value wrong
for the password:
mutation {
auth(email: "demo", password: "wrong") {
token
}
}
This mutation will return a token for the user if the credentials are valid or throw an error if the credentials are invalid. In this case, the password is wrong, as it should be demo
. The errors
field will contain a list of errors that occurred during the execution of the request. The errors
field is a list of objects with the following fields:
{
"data": null,
"errors": [
{
"message": "Cannot return null for non-nullable field Token.token.",
"locations": [
{
"line": 3,
"column": 5
}
],
"path": [
"auth",
"token"
]
}
]
}
From the error message, we learn that the response doesn't contain a token because the credentials aren't valid. The locations
field has the line and column number of the error in the GraphQL query. The path
field contains the path to the field that caused the error. As both the types Token
and the field token
are non-nullable, the GraphQL engine will throw an error.
Extensions
Some GraphQL engines will use extensions
to make this error more readable. In these extensions, implementors can define more information about why the GraphQL server threw the error:
{
"data": null,
"errors": [
{
"message": "Cannot return null for non-nullable field Token.token.",
"locations": [
{
"line": 3,
"column": 5
}
],
"path": [
"auth",
"token"
],
"extensions": {
"code": "INVALID_CREDENTIALS",
"message": "Password is invalid"
}
}
]
}
The GraphQL specification doesn't define what the extensions
field should contain, so it's up to the implementor to define what additional information the extensions
field should include.
But next to using the extensions
field to add more information about the error, there's an even more readable way to handle errors in GraphQL.
Errors as Data
Handling errors using the errors
field and optionally providing more info through the extensions
field is a good way of handling errors. But there's an even more readable way of handling errors in GraphQL, often called the "errors as data" approach.
The idea behind the "errors as data" approach is that you no longer have to look at the errors
field to see if there were any errors during the execution of the request. Instead, you can request fields based on a successful response and fields for an error response. This way, your error handling is part of the schema.
To implement the "errors as data" approach we need to use Union
types. This type will define the response type for when the request is successful and the response type that is returned when the request fails. Let's take a look at the following example:
type Token {
id: ID!
token: String!
}
type AuthError {
message: String!
}
union Auth = Token | AuthError
type Mutation {
auth(email: String!, password: String!): Auth
}
The mutation auth
now returns a Union
type called Auth
. The Auth
type can be either a Token
or an AuthError
. The Token
type is the same as the one we used in the previous example. The AuthError
type has a single field called message
that is a non-nullable string that can contain the error message that is normally be returned in the errors
or extensions
field.
When using this mutation to authenticate the user, you need to use inline fragments to define what fields you'd like to be returned, both for a successful response and for an error response:
mutation {
auth(email: "demo", password: "demo") {
... on Token {
id
token
}
... on AuthError {
message
}
}
}
When the combination of the values for email
and password
is correct, the mutation will resolve to the type Token
and return the id
and token
fields. When the combination of the values for email
and password
is incorrect, the mutation will resolve to the type AuthError
and return the message
field.
The "errors as data" approach has several advantages:
- It's more readable because the error handling is part of the schema.
- It's more flexible because you can return different fields for errors.
- It's more declarative because you don't have to look at the
errors
field to see if there were any errors during the execution of the request.
When using StepZen as your GraphQL engine, you can use Union
types to implement the "errors as data" approach, no matter what data source you're trying to connect. Have a look at the snippets repository to see how you can implement the "errors as data" approach using StepZen.
We'd love to hear what you're building using GraphQL and StepZen! Ping us on Twitter or join our Discord community to get in touch.