Nullability in GraphQL is a controversial topic. Some say constant null checks are a nuisance, while others err on the side of paranoia. I say, UFO's are real. But, that's not the point. In this post, you'll hear both sides of the discussion, find a cheatsheet you can refer to later, and read about different approaches to nulls and errors by Apollo and Relay.
- Nullability and error handling in GraphQL
- Sending nulls in GraphQL queries
- Receiving nulls in GraphQL responses
- Finding the true meaning of null
- Anatomy of GraphQL errors
- Pros & cons
- Different approaches by GraphQL clients
Nullability and error handling in GraphQL
So what's all the ruckus about? It all starts with GraphQL's unconventional approach to defaults and error handling.
In other typed languages, like TypeScript, Flow, Rust, or Haskell, all types are non-nullable by default, and you opt into nullability. But in GraphQL, all types are nullable by default, and you opt into non-nullability. In fact, Non-Null is a type that wraps your original type when you opt in.
TypeScript:
interface User {
  name?: string // opting into optional name
  name: string | null // opting into nullable name
}
GraphQL:
type User {
  name: String! // opting into non-nullable name
}
As if that's not enough fodder for DRAMZ, GraphQL breaks with the REST convention of relying on HTTP status codes for error handling. Even if there's an error in the GraphQL layer while executing your request, the response is still  200 OK 😲
Then how does the client know about errors? This is where nulls become relevant. The server returns a root field called errors, from which the client can extract them, and a root field called data that has all the requested fields. To top it off, any fields with errors have the value null.
All of this to say: all types in GraphQL are nullable by default in case of an error. GraphQL doesn't let a small hiccup get in the way of our data fetching: when there's an error with a field, we still get partial data.
We'll debate the pros and cons in a bit, including different approaches by Apollo and Relay. But first, the promised cheatsheet...
Nulls in the query
A GraphQL query can have fields and inputs, with or without variables.
Fields are always optional, whether nullable or not.
type User {
  name: String! // non-null
  age: Int // nullable
}
{
  user {
    name // optional
    age // optional
  }
}
In inputs, such as field arguments, nullable types are always optional and non‐null types are always required.
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
  }
}
In addition, a variable of a nullable type cannot be provided to a non‐null argument.
query user($imageSize: String) { // nullable variable
  imageUrl(size: $imageSize) // field with non-null argument
}
Nulls in the response
If you get a null value for a nullable field from the server, there are four possibilities:
- 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?
The GraphQL spec says that a null result on a Non-Null type bubbles up to the next nullable parent. If this bubbling never stops because everything is of Non-Null type, then the root data field is null.
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",
    // ...
  }
]
When fields are nullable, as in the first example, you still get partial data: even if you don't have the name, you can still show the age and other fields. When you mark fields as Non-Null, as in the second example, you forfeit your right to partial data.
  
  
  How can I know the true meaning of null?
The four possibilities of null leave us with three questions:
- 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?
And the answers lie in the errors list returned in the response.
If an error is thrown while resolving a field, it should be treated as though the field returned
null, and an error must be added to theerrorslist in the response.
  
  
  Anatomy of the errors list
The errors list must include message, locations, and path entries, with an optional extensions entry.
"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'
    }
  }
]
The path entry answers our first two questions: whether the null result is intentional or due to an error, and what field it's on. The extensions entry answers our third question by allowing us to specify the type of error, and anything else we can possibly divine: the time of day when it happened, the weather outside, etc.
In the case of a  Non-Null field, the path entry still specifies the correct source of the error, even when the data returned points to the parent as the troublemaker.
data: { 
  user: null
},
errors: [
  { 
    message: "Name could not be fetched",
    path: ["user", 0, "name"]
    // ...
  },
  // ...
]
Pros & cons
Now that we've got the cheatsheet down, let's talk about the pros and cons of nullable vs Non-Null types.
Benefits of nullable types
We've already covered one major benefit:
- 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).
Other benefits:
- 
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
If you find null checks cumbersome,  Non-Null is the type for you. Why?
- You get guaranteed values. If you know that locationis non-nullable, you can just do this:
if (user) return user.location.city;
If location returns null it will bubble up to user, and user.location.city will never execute. location is guaranteed to never be null, so you never need a null check on it.
- 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?
Though this is a hotly debated topic, the general recommendation is to make everything nullable except when it doesn't make sense, e.g. getUserById(id: ID!), or the value is guaranteed to exist, e.g. a primary key like id. But you can weigh the pros and cons above, and decide for yourself! (If you do, please let me know in the comments).
Different approaches by GraphQL clients
Depending on what GraphQL client you're using, you may be able to pick your strategy on a per-query basis, or come up with a different solution to error handling altogether.
Apollo: Everything is possible
When it comes to error handling, Apollo gives you options. Three options, in fact.
- 
none(default): Treat GraphQL errors like network errors, ignore data
- 
ignore: Get data, ignore errors
- 
all: Get both data & errors
The all option matches the GraphQL spec, and will allow you to get partial data in case of an error. You can set the errorPolicy on a per-query basis, like so:
const { loading, error, data } = useQuery(MY_QUERY, { errorPolicy: 'all' });
Relay: All is good all the time
Relay is all about living the good life. A little GraphQL error here and there isn't enough to mess with Relay's zen thing.
That's right. Relay ignores GraphQL errors unless:
- the fetch function provided to the Relay Network throws or returns an Error
- the top-level datafield isn't returned in the response
The Relay docs recommend modeling errors in your schema rather than returning null.
type Error {
  message: String!
}
type User {
  name: String | Error
}
For a detailed breakdown of how you might model this in your schema, check out this excellent blog post by Laurin Quast.
Conclusion
I hope this article has helped you master nulls in GraphQL. We covered:
- 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
Where do you stand in this discussion? What has been your experience with nullability? Let us know on Twitter or in the comments below!
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
 

 
                      

 
    
Top comments (1)
In GraphQL, handling nulls is crucial for ensuring data integrity and preventing unexpected errors. Fields can return null due to missing data, resolver failures, or permission restrictions. Using Non-Null (!) types, proper error handling, and default values can help manage nullability effectively. Just as players seek a smooth experience when downloading Nulls Brawl Android, developers rely on GraphQL’s structured approach to handle nulls efficiently, ensuring reliable and predictable API responses.