Nulls in GraphQL: Cheatsheet
Nullability and error handling in GraphQL
interface User {
name?: string // opting into optional name
name: string | null // opting into nullable name
}
type User {
name: String! // opting into non-nullable name
}
Nulls in the query
type User {
name: String! // non-null
age: Int // nullable
}
{
user {
name // optional
age // optional
}
}
type User {
// nullable filter input, non-null size input
imageUrl(filter: String, size: String!): String
}
{
user {
imageUrl // not valid
imageUrl(size: null) // not valid
imageUrl(size: 'med') // valid
imageUrl(filter: null, size: 'med') // valid
imageUrl(filter: 'juno', size: 'med') // valid
}
}
query user($imageSize: String) { // nullable variable
imageUrl(size: $imageSize) // field with non-null argument
}
Nulls in the response
- The value is actually null or absent, e.g. user doesn't exist
- The value is hidden, e.g. user has blocked you (permission error)
- There was another error fetching the value
- An error or
null
value has bubbled up from aNon-Null
child
Wait, what was that about bubbling up?
Let's say name
is nullable, but the resolver returns an error while fetching it:
data: {
user: {
name: null,
age: 25,
// other fields on user
}
},
errors: [
{
message: "Name could not be fetched",
// ...
}
]
If name
is non-nullable and user
is nullable, this is what happens instead:
data: {
user: null
},
errors: [
{
message: "Name could not be fetched",
// ...
}
]
How can I know the true meaning of null
?
- Do we have an actual absent value or an error?
- If it's an error, which field is it on?
- What kind of error is it?
If an error is thrown while resolving a field, it should be treated as though the field returnednull
, and an error must be added to theerrors
list in the response.
- GraphQL Spec
Anatomy of the errors
list
"errors": [
{
// error message from the resolver
"message": "Name for user with ID 42 could not be fetched.",
// map to the field in the GraphQL document
"locations": [ { "line": 3, "column": 5 } ],
// path to the field
"path": [ "user", 0, "name" ],
// optional map with additional info
"extensions": {
code: PERMISSION_ERROR,
weatherOutside: 'weather'
}
}
]
data: {
user: null
},
errors: [
{
message: "Name could not be fetched",
path: [ "user", 0, "name" ]
// ...
},
// ...
]
Pros & cons
Benefits of nullable types
- When the HTTP status code is
200 OK
, we're able to get partial data from the server, despite errors on specific fields. But we still need a way to tell if something went wrong, which we can achieve with anull
value on the erroneous field, along with anerrors
list (Btw, we'll discuss another solution to this – returning anError
type instead ofnull
– in the Relay section below).
- Privacy when you want to obfuscate the reasons for
null
. Maybe you don't want the client to know whether you got anull
onuser
because the user has blocked you or because the user simply doesn't exist. If you're a ghoster, this is the way to go. - If you're serving different clients from a single schema, nullable fields are more reliable, and easier to evolve. For example, if you remove a
Non-Null
field from the schema, a client that you have no control ever may break when it receivesnull
for that field. - Coding defensively on the client side. Null check all the things!
if (user && user.location) return user.location.city;
Benefits of Non-Null types
- You get guaranteed values. If you know that
location
is non-nullable, you can just do this:
if (user) return user.location.city;
- You can combine
Non-Null
types with query type generation to make your code even more predictable. For example, with TypeScript, the generated code for a users query can be:
type GetUsersQuery = {
users: Array<{
__typename: "User",
name: string // if Non-Null in schema
name: string | null // if nullable in schema
}
};
- Easy error detection. If you get a
null
value for a non-nullable field, you know it's because of an error, since it can't be a legitimate absent value.
So how should I design my schema?
Different approaches by GraphQL clients
Apollo: Everything is possible
none
(default): Treat GraphQL errors like network errors, ignore dataignore
: Get data, ignore errorsall
: Get both data & errors
const { loading, error, data } = useQuery(MY_QUERY, { errorPolicy: 'all' });
Relay: All is good all the time
- the fetch function provided to the Relay Network throws or returns an Error
- the top-level
data
field isn't returned in the response
type Error {
message: String!
}
type User {
name: String | Error
}
Conclusion
- GraphQL's approach to nullability and error handling
- What nulls mean in GraphQL queries and responses
- Pros & cons of nullable and Non-Null types
- Different approaches to nulls and error handling by Apollo and Relay
Further Reading
- Using nullability in GraphQL, by Sashko Stubailo
- Nullability in GraphQL, by Grant Norwood
- When To Use GraphQL Non-Null Fields, by Caleb Meredith
- Handling GraphQL errors like a champ with unions and interfaces, by Laurin Quast
- GraphQL Best Practices: Nullability
- GraphQL Spec
- Handling errors with Apollo
- Relay: Accessing errors in GraphQL Response