Skip to content

Commit e24e6a9

Browse files
docs: add guide on nullability (#4405)
Adds "Nullability in GraphQL.js" guide
1 parent 1f1d89d commit e24e6a9

File tree

2 files changed

+229
-0
lines changed

2 files changed

+229
-0
lines changed

website/pages/docs/_meta.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -17,6 +17,7 @@ const meta = {
1717
title: 'Advanced Guides',
1818
},
1919
'constructing-types': '',
20+
nullability: '',
2021
'abstract-types': '',
2122
'oneof-input-objects': '',
2223
'defer-stream': '',

website/pages/docs/nullability.mdx

Lines changed: 228 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,228 @@
1+
---
2+
title: Nullability
3+
sidebarTitle: Nullability in GraphQL.js
4+
---
5+
6+
# Nullability in GraphQL.js
7+
8+
Nullability is a core concept in GraphQL that affects how schemas are defined,
9+
how execution behaves, and how clients interpret results. In GraphQL.js,
10+
nullability plays a critical role in both schema construction and
11+
runtime behavior.
12+
13+
This guide explains how nullability works, how it's represented in GraphQL.js,
14+
and how to design schemas with nullability in mind.
15+
16+
## How nullability works
17+
18+
In GraphQL, fields are nullable by default. This means if a resolver function
19+
returns `null`, the result will include a `null` value unless the field is
20+
explicitly marked as non-nullable.
21+
22+
When a non-nullable field resolves to `null`, the GraphQL execution engine
23+
raises a runtime error and attempts to recover by replacing the nearest
24+
nullable parent field with `null`. This behavior is known
25+
as null bubbling.
26+
27+
Understanding nullability requires familiarity with the GraphQL type system,
28+
execution semantics, and the trade-offs involved in schema design.
29+
30+
## The role of `GraphQLNonNull`
31+
32+
GraphQL.js represents non-nullability using the `GraphQLNonNull` wrapper type.
33+
By default, all fields are nullable:
34+
35+
```js
36+
import {
37+
GraphQLObjectType,
38+
GraphQLString,
39+
GraphQLNonNull,
40+
} from 'graphql';
41+
42+
const UserType = new GraphQLObjectType({
43+
name: 'User',
44+
fields: () => ({
45+
id: { type: new GraphQLNonNull(GraphQLString) },
46+
email: { type: GraphQLString },
47+
}),
48+
});
49+
```
50+
51+
In this example, the `id` field is non-nullable, meaning it must always
52+
resolve to a string. The `email` field is nullable.
53+
54+
You can use `GraphQLNonNull` with:
55+
56+
- Field types
57+
- Argument types
58+
- Input object fields
59+
- Return types for resolvers
60+
61+
You can also combine it with other types to create more
62+
specific constraints. For example:
63+
64+
```js
65+
new GraphQLNonNull(new GraphQLList(new GraphQLNonNull(UserType)))
66+
```
67+
68+
This structure corresponds to [User!]! in SDL: a non-nullable list of non-null
69+
`User` values. When reading code like this, work from the inside out: `UserType`
70+
is non-nullable, and wrapped in a list, which is itself non-nullable.
71+
72+
## Execution behavior
73+
74+
GraphQL.js uses nullability rules to determine how to handle `null` values
75+
at runtime:
76+
77+
- If a nullable field returns `null`, the result includes that field with
78+
a `null` value.
79+
- If a non-nullable field returns `null`, GraphQL throws an error and
80+
sets the nearest nullable parent field to `null`.
81+
82+
This bubbling behavior prevents partial data from being returned in cases where
83+
a non-nullable guarantee is violated.
84+
85+
Here's an example that shows this in action:
86+
87+
```js
88+
import {
89+
GraphQLSchema,
90+
GraphQLObjectType,
91+
GraphQLString,
92+
GraphQLNonNull,
93+
} from 'graphql';
94+
95+
const UserType = new GraphQLObjectType({
96+
name: 'User',
97+
fields: {
98+
id: { type: new GraphQLNonNull(GraphQLString) },
99+
},
100+
});
101+
102+
const QueryType = new GraphQLObjectType({
103+
name: 'Query',
104+
fields: {
105+
user: {
106+
type: UserType,
107+
resolve: () => ({ id: null }),
108+
},
109+
},
110+
});
111+
112+
const schema = new GraphQLSchema({ query: QueryType });
113+
```
114+
115+
In this example, the `user` field returns an object with `id: null`.
116+
Because `id` is non-nullable, GraphQL can't return `user.id`, and instead
117+
nullifies the `user` field entirely. An error describing the violation is
118+
added to the `errors` array in the response.
119+
120+
## Schema design considerations
121+
122+
Using non-null types communicates clear expectations to clients, but it's
123+
also less forgiving. When deciding whether to use `GraphQLNonNull`, keep
124+
the following in mind:
125+
126+
- Use non-null types when a value is always expected. This reflects intent
127+
and reduces ambiguity for clients.
128+
- Avoid aggressive use of non-null types in early schema versions. It limits
129+
your ability to evolve the API later.
130+
- Be cautious of error bubbling. A `null` return from a deeply nested non-nullable
131+
field can affect large portions of the response.
132+
133+
### Versioning
134+
135+
Non-null constraints are part of a field's contract:
136+
137+
- Changing a field from non-nullable to nullable is a breaking change.
138+
- Changing from nullable to non-nullable is also breaking unless you can
139+
guarantee that the field will never return `null`.
140+
141+
To reduce the risk of versioning issues, start with nullable fields and add
142+
constraints as your schema matures.
143+
144+
## Using `GraphQLNonNull` in schema and resolvers
145+
146+
Let's walk through two practical scenarios that show how GraphQL.js enforces
147+
nullability.
148+
149+
### Defining a non-null field
150+
151+
This example defines a `Product` type with a non-nullable `name` field:
152+
153+
```js
154+
import { GraphQLObjectType, GraphQLString, GraphQLNonNull } from 'graphql';
155+
156+
const ProductType = new GraphQLObjectType({
157+
name: 'Product',
158+
fields: () => ({
159+
name: { type: new GraphQLNonNull(GraphQLString) },
160+
}),
161+
});
162+
```
163+
164+
This configuration guarantees that `name` must always be a string
165+
and never `null`. If a resolver returns `null` for this field, an
166+
error will be thrown.
167+
168+
### Resolver returns `null` for a non-null field
169+
170+
In this example, the resolver returns an object with `name: null`, violating
171+
the non-null constraint:
172+
173+
```js
174+
import {
175+
GraphQLObjectType,
176+
GraphQLString,
177+
GraphQLNonNull,
178+
GraphQLSchema,
179+
} from 'graphql';
180+
181+
const ProductType = new GraphQLObjectType({
182+
name: 'Product',
183+
fields: {
184+
name: { type: new GraphQLNonNull(GraphQLString) },
185+
},
186+
});
187+
188+
const QueryType = new GraphQLObjectType({
189+
name: 'Query',
190+
fields: {
191+
product: {
192+
type: ProductType,
193+
resolve: () => ({ name: null }),
194+
},
195+
},
196+
});
197+
198+
const schema = new GraphQLSchema({ query: QueryType });
199+
```
200+
201+
In this example, the `product` resolver returns an object with `name: null`.
202+
Because the `name` field is non-nullable, GraphQL.js responds by
203+
nullifying the entire `product` field and appending a
204+
corresponding error to the response.
205+
206+
## Best practices
207+
208+
- Default to nullable. Start with nullable fields and introduce non-null
209+
constraints when data consistency is guaranteed.
210+
- Express intent. Use non-null when a field must always be present for logical
211+
correctness.
212+
- Validate early. Add checks in resolvers to prevent returning `null` for
213+
non-null fields.
214+
- Watch for nesting. Distinguish between:
215+
- `[User]!` - nullable list of non-null users
216+
- `[User!]!` - non-null list of non-null users
217+
218+
## Additional resources
219+
220+
- [GraphQL Specification: Non-null](https://spec.graphql.org/draft/#sec-Non-Null):
221+
Defines the formal behavior of non-null types in the GraphQL type system and
222+
execution engine.
223+
- [Understanding GraphQL.js Errors](website\pages\docs\graphql-errors.mdx): Explains
224+
how GraphQL.js propagates and formats execution-time errors.
225+
- [Anatomy of a Resolver](website\pages\docs\resolver-anatomy.mdx): Breaks down
226+
how resolvers work in GraphQL.js.
227+
- [Constructing Types](website\pages\docs\constructing-types.mdx): Shows how
228+
to define and compose types in GraphQL.js.

0 commit comments

Comments
 (0)