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
nullvalue has bubbled up from aNon-Nullchild
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 theerrorslist 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 anullvalue on the erroneous field, along with anerrorslist (Btw, we'll discuss another solution to this – returning anErrortype 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 anullonuserbecause 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-Nullfield from the schema, a client that you have no control ever may break when it receivesnullfor 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
locationis non-nullable, you can just do this:
if (user) return user.location.city;- You can combine
Non-Nulltypes 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
nullvalue 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
datafield 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
Ready to get started with GraphQL's unconventional approach to defaults and error handling?
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



