Nullability in GraphQL.js
Nullability is a core concept in GraphQL that affects how schemas are defined, how execution behaves, and how clients interpret results. In GraphQL.js, nullability plays a critical role in both schema construction and runtime behavior.
This guide explains how nullability works, how it’s represented in GraphQL.js, and how to design schemas with nullability in mind.
How nullability works
In GraphQL, fields are nullable by default. This means if a resolver function
returns null, the result will include a null value unless the field is
explicitly marked as non-nullable.
When a non-nullable field resolves to null, the GraphQL execution engine
raises a runtime error and attempts to recover by replacing the nearest
nullable parent field with null. This behavior is known formally as “error
propagation” but more commonly as null bubbling.
Understanding nullability requires familiarity with the GraphQL type system, execution semantics, and the trade-offs involved in schema design.
The role of GraphQLNonNull
GraphQL.js represents non-nullability using the GraphQLNonNull wrapper type.
By default, all fields are nullable:
import {
GraphQLObjectType,
GraphQLString,
GraphQLNonNull,
} from 'graphql';
const UserType = new GraphQLObjectType({
name: 'User',
fields: () => ({
id: { type: new GraphQLNonNull(GraphQLString) },
email: { type: GraphQLString },
}),
});In this example, the id field is non-nullable, meaning it must always
resolve to a string. The email field is nullable.
You can use GraphQLNonNull with:
- Field types
- Argument types
- Input object field types
You can also combine it with other types to create more specific constraints. For example:
new GraphQLNonNull(new GraphQLList(new GraphQLNonNull(UserType)))This structure corresponds to [User!]! in SDL: a non-nullable list of non-null
User values. When reading code like this, work from the inside out: UserType
is non-nullable, and wrapped in a list, which is itself non-nullable.
Execution behavior
GraphQL.js uses nullability rules to determine how to handle null values
at runtime:
- If a nullable field returns
null, the result includes that field with anullvalue. - If a non-nullable field returns
null, GraphQL throws an error and sets the nearest nullable parent field tonull.
This bubbling behavior prevents partial data from being returned in cases where a non-nullable guarantee is violated.
Here’s an example that shows this in action:
import {
GraphQLSchema,
GraphQLObjectType,
GraphQLString,
GraphQLNonNull,
} from 'graphql';
const UserType = new GraphQLObjectType({
name: 'User',
fields: {
id: { type: new GraphQLNonNull(GraphQLString) },
},
});
const QueryType = new GraphQLObjectType({
name: 'Query',
fields: {
user: {
type: UserType,
resolve: () => ({ id: null }),
},
},
});
const schema = new GraphQLSchema({ query: QueryType });In this example, the user field returns an object with id: null.
Because id is non-nullable, GraphQL can’t return user.id, and instead
nullifies the user field entirely. An error describing the violation is
added to the errors array in the response.
Schema design considerations
Using non-null types communicates clear expectations to clients, but it’s
also less forgiving. When deciding whether to use GraphQLNonNull, keep
the following in mind:
- Use non-null types when a value is always expected. This reflects intent and reduces ambiguity for clients.
- Avoid aggressive use of non-null types in early schema versions. It limits your ability to evolve the API later.
- Be cautious of error bubbling. A
nullreturn from a deeply nested non-nullable field can affect large portions of the response.
Versioning
Non-null constraints are part of a field’s contract:
- Changing an output position (field type) from non-nullable to nullable is a
breaking change - clients may now receive
nullvalues which they do not have handling code for. - Changing an input position (argument or input field type) from nullable to non-nullable is a breaking change - clients are now required to provide this value, which they may not have been supplying previously.
- Changing an output position from nullable to non-nullable will not break deployed clients since their null handling code will simply not be exercised.
To reduce the risk of versioning issues, start with nullable fields and add constraints as your schema matures.
Using GraphQLNonNull in schema and resolvers
Let’s walk through two practical scenarios that show how GraphQL.js enforces nullability.
Defining a non-null field
This example defines a Product type with a non-nullable name field:
import { GraphQLObjectType, GraphQLString, GraphQLNonNull } from 'graphql';
const ProductType = new GraphQLObjectType({
name: 'Product',
fields: () => ({
name: { type: new GraphQLNonNull(GraphQLString) },
}),
});This configuration guarantees that name must always be a string
and never null. If a resolver returns null for this field, an
error will be thrown.
Resolver returns null for a non-null field
In this example, the resolver returns an object with name: null, violating
the non-null constraint:
import {
GraphQLObjectType,
GraphQLString,
GraphQLNonNull,
GraphQLSchema,
} from 'graphql';
const ProductType = new GraphQLObjectType({
name: 'Product',
fields: {
name: { type: new GraphQLNonNull(GraphQLString) },
},
});
const QueryType = new GraphQLObjectType({
name: 'Query',
fields: {
product: {
type: ProductType,
resolve: () => ({ name: null }),
},
},
});
const schema = new GraphQLSchema({ query: QueryType });In this example, the product resolver returns an object with name: null.
Because the name field is non-nullable, GraphQL.js responds by
nullifying the entire product field and appending a
corresponding error to the response.
Best practices
- Default to nullable. Start with nullable fields and introduce non-null constraints when data consistency is guaranteed.
- Express intent. Use non-null when a field must always be present for logical correctness.
- Validate early. Add checks in resolvers to prevent returning
nullfor non-null fields. - Consider error boundaries. Were an error to occur, where should it stop bubbling?
- Watch for nesting. Distinguish between:
[User!]- nullable list of non-null users[User]!- non-null list of nullable users[User!]!- non-null list of non-null users
Additional resources
- GraphQL Specification: Non-null: Defines the formal behavior of non-null types in the GraphQL type system and execution engine.
- Understanding GraphQL.js Errors: Explains how GraphQL.js propagates and formats execution-time errors.
- Anatomy of a Resolver: Breaks down how resolvers work in GraphQL.js.
- Constructing Types: Shows how to define and compose types in GraphQL.js.