One of the main features of the GraphQL specification is its type system. The type system is coupled to the GraphQL Schema, closely to the data definitions. Because of this type system, you know what fields are available to use in your operations. By knowing which fields you can query, you can prevent your requests from failing. Ever wondered what the exclamation mark (!) in a GraphQL schema was for? It's for something called nullability. GraphQL will throw an error when you use non-existing fields in your query or when the response contains values that aren't present in the schema.

In this post, you'll learn about nullability in GraphQL and why it matters when designing a GraphQL schema.

What is nullability in GraphQL?

Nullability is important in the type system of GraphQL as it determines whether a field can be null or non-nullable. In most computer programming languages, you can use the value null to give something a zero value. So if the value of a field could be null, you need to make that field type nullable. In GraphQL, every type is nullable by default unless you append a exclamation point (!) to the type. Let's look at some examples.

Nullable fields

The example GraphQL schema below shows a type called Post that has three fields:

type Post {
  id: ID!
  title: String
  body: String
}

type Query {
  getPosts: [Post]
}

Both title and body are nullable, meaning the GraphQL API could return the value null if you request them in your query. Only the field id is non-nullable, meaning its value can only be the scalar type ID, or the GraphQL API will throw an error.

Nullability also exists for relational fields. Let's extend the GraphQL schema from the previous example and add a new type called Author:

type Post {
  id: ID!
  title: String
  body: String
  author: Author!
}

type Author {
  id: ID!
  name: String
}

type Query {
  getPosts: [Post]
}

When you query getPosts and include the field author, you'll be guaranteed this value is never null.

Nullable lists

You can apply the same principle to lists, where nullability can be confusing. Let's look again at the getPosts query from our previous example:

type Query {
  getPosts: [Post]
}

The return type of this query is [Post], which is nullable as no exclamation mark (!) is present in the schema. So the response of your GraphQL API for this query can be null, an empty array ([]) or an array of objects with the type Post.

However, if we change the response type to be non-nullable, we've got multiple options.

For lists, you can rewrite the response type as:

  • [Post]!: The response must be an array that can be empty ([]), include null values ([null]), or be an array of Post
  • [Post!]: The response can be null, an empty array ([]), or an array of Post. It cannot be an array that includes null values.

Or a combination of the two:

  • [Post!]!: The response must be an array that can either be empty ([]) or is an array of Post.

As you can see, the response value can always be an empty array ([]), meaning that you cannot use nullability to prevent empty lists from being returned.

Why nullability matters

You've already learned about nullability in GraphQL, both for (relational) fields and lists. When looking at the schema of a GraphQL API, you can use the information about what fields are nullable or non-nullable to simplify your code.

Assume you've got the following query retrieving posts with the getPosts query, including the relational field author. If you use this query from a frontend JavaScript application, it will look like the following:

import { request, gql } from 'graphql-request'

const query = gql`
  {
    getPosts {
      title
      author {
        name
      }
    }
  }
`

request('https://my.graphql.api', query).then((data) => console.log(data))

Without looking at the schema, you don't know if any of these fields will resolve in your response. As you explicitly need to make fields non-nullable in GraphQL, it's safe to assume everything can be null. Meaning you need to destructure the field name of the relational field author:

request('https://my.graphql.api', query)
  .then((data) => {
    const authorName = data.getPosts?.author?.name

    console.log(authorName)
  })

When you know the field author and/or name are non-nullable, you can skip the optional chaining operators in your code. Even when you're not using a programming language with a type system on the frontend, you can make your code more stable using the GraphQL type system.

The simplifications you can do are even more significant when working with lists. Imagine you want to iterate over all the posts and need to know if the response is either null, an empty array, or an array filled with null or Post.

request('https://my.graphql.api', query)
  .then((data) => {
    const posts = data.getPosts

    if (posts && posts.length > 0 && !posts.includes(null)) {
      posts.map(post => console.log(post))
    }
  })

Earlier, we've seen the number of possibilities for (non-)nullable lists, and above, you can see the number of checks you need to check if the response of the getPosts query is valid. Without control over the GraphQL schema, you cannot simplify this, but you can have control over the schema. By changing the response type of getPosts to [Post!]!, you can skip the check for posts in the response and the check to see if the array contains null.

There is one important notice I'd like to make here. You can only take full advantage of the type system of GraphQL when you apply the GraphQL specification correctly. If your resolver returns the value null for a non-nullable field, your GraphQL API will always throw an error. When writing your resolver code, make sure it produces a response that fits your GraphQL schema.

Nullability is one of the many things you need to think about when building a GraphQL API. If you're currently deciding whether to build something from scratch or use a tool like StepZen to create a GraphQL API, have a look at this article I wrote earlier. When using StepZen, you don't have to worry about catching any nullability errors. The GraphQL schema you define will get checked during compile time before deploying your endpoint to StepZen.

Want to learn more about StepZen? Try it out here or ask any question on the Discord here.