Understanding Nullability Semantics
1. Understanding Null Propagation
Example: Null bubbles up
# Schema
type Query {
user: User
}
type User {
id: ID!
name: String! # non-null
}
# If name resolves to null:
# → User becomes null (User.user is nullable)
# → errors[] contains the path
| Path | Result |
|---|---|
| All non-null up to root | Entire data becomes null |
| Nullable ancestor exists | That ancestor becomes null |
| Sibling fields | Preserved (partial data) |
2. Making Fields Non-nullable
| When to use ! | When to avoid |
|---|---|
| Truly always present (id, createdAt) | Depends on permissions |
| Computed from required state | Could fail (network, integration) |
| List fields (prefer empty array) | External data sources |
3. Making Arguments Non-nullable
| Form | Effect |
|---|---|
arg: T! | Must be supplied, cannot be null |
arg: T! = default | Required type, default if omitted |
arg: T | Optional, defaults to null |
4. Handling Null Values
| Strategy | Use |
|---|---|
| Null guard in resolver | if (!parent.x) return null; |
| Optional chaining | parent?.address?.city |
| Default value | Coalesce in resolver |
5. Using Null as Default
| Field | Reasoning |
|---|---|
| Optional update fields | null = unchanged |
| Optional filters | null = no filter applied |
| Computed fields | null = not yet computed |
6. Validating Null Inputs
Example: Distinguish omitted vs explicit null
updateUser(_, { id, input }) {
const update = {};
if ("name" in input) update.name = input.name; // null = clear
if ("email" in input) update.email = input.email;
return db.user.update({ where: { id }, data: update });
}
7. Returning Null vs Empty
| Return | Semantics |
|---|---|
| null on object | "Not found" or "no value" |
| [] on list | "No items" |
| "" on string | Avoid — ambiguous; prefer null |
8. Null in Lists
| Type | Null Behavior |
|---|---|
[T!]! | Any null item or null list → entire list bubbles error |
[T]! | List required but item nulls allowed |
[T!] | Item must be non-null OR entire list becomes null |
[T] | Anything goes |
9. Designing Null-safe Schemas
| Guideline | Detail |
|---|---|
| Default nullable | Add ! only when guaranteed |
| Critical IDs non-null | id: ID! always |
| External data nullable | Third-party calls may fail |
Lists: [T!]! | Empty array is enough — no need for null |
10. Using Maybe Types
| Codegen | TypeScript Output |
|---|---|
| String! | string |
| String | Maybe<string> = string | null | undefined |
| [String!]! | string[] |
| [String] | Maybe<Array<Maybe<string>>> |