From ba7ffa8a62ec2716a3c6ccd09ba018083d1c2d83 Mon Sep 17 00:00:00 2001 From: sarahxsanders Date: Wed, 21 May 2025 17:33:30 -0400 Subject: [PATCH 1/4] add guide on nullability --- website/pages/docs/_meta.ts | 1 + website/pages/docs/nullability.mdx | 228 +++++++++++++++++++++++++++++ 2 files changed, 229 insertions(+) create mode 100644 website/pages/docs/nullability.mdx diff --git a/website/pages/docs/_meta.ts b/website/pages/docs/_meta.ts index 9b09249e88..8cf078bdfa 100644 --- a/website/pages/docs/_meta.ts +++ b/website/pages/docs/_meta.ts @@ -17,6 +17,7 @@ const meta = { title: 'Advanced Guides', }, 'constructing-types': '', + 'nullability': '', 'abstract-types': '', 'oneof-input-objects': '', 'defer-stream': '', diff --git a/website/pages/docs/nullability.mdx b/website/pages/docs/nullability.mdx new file mode 100644 index 0000000000..7d514b334f --- /dev/null +++ b/website/pages/docs/nullability.mdx @@ -0,0 +1,228 @@ +--- +title: Nullability +sidebarTitle: Nullability in GraphQL.js +--- + +# 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 +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: + +```js +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 fields +- Return types for resolvers + +You can also combine it with other types to create more +specific constraints. For example: + +```js +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 +a `null` value. +- If a non-nullable field returns `null`, GraphQL throws an error and +sets the nearest nullable parent field to `null`. + +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: + +```js +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 `null` return 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 a field from non-nullable to nullable is a breaking change. +- Changing from nullable to non-nullable is also breaking unless you can +guarantee that the field will never return `null`. + +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: + +```js +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: + +```js +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 `null` for +non-null fields. +- Watch for nesting. Distinguish between: + - `[User]!` - nullable list of non-null users + - `[User!]!` - non-null list of non-null users + +## Additional resources + +- [GraphQL Specification: Non-null](https://spec.graphql.org/draft/#sec-Non-Null): +Defines the formal behavior of non-null types in the GraphQL type system and +execution engine. +- [Understanding GraphQL.js Errors](website\pages\docs\graphql-errors.mdx): Explains +how GraphQL.js propagates and formats execution-time errors. +- [Anatomy of a Resolver](website\pages\docs\resolver-anatomy.mdx): Breaks down +how resolvers work in GraphQL.js. +- [Constructing Types](website\pages\docs\constructing-types.mdx): Shows how +to define and compose types in GraphQL.js. From bbeea1fe4613a35e919f7b7596e5c5868e69ff13 Mon Sep 17 00:00:00 2001 From: sarahxsanders Date: Wed, 21 May 2025 17:41:43 -0400 Subject: [PATCH 2/4] fix prettier failure --- website/pages/docs/_meta.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/website/pages/docs/_meta.ts b/website/pages/docs/_meta.ts index 8cf078bdfa..998747d411 100644 --- a/website/pages/docs/_meta.ts +++ b/website/pages/docs/_meta.ts @@ -17,7 +17,7 @@ const meta = { title: 'Advanced Guides', }, 'constructing-types': '', - 'nullability': '', + nullability: '', 'abstract-types': '', 'oneof-input-objects': '', 'defer-stream': '', From 5fb54fe1e55764b0e79dba1198469d7bab69cb68 Mon Sep 17 00:00:00 2001 From: sarahxsanders Date: Wed, 21 May 2025 20:56:52 -0400 Subject: [PATCH 3/4] fix _meta.ts --- website/pages/docs/_meta.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/website/pages/docs/_meta.ts b/website/pages/docs/_meta.ts index 998747d411..8cf078bdfa 100644 --- a/website/pages/docs/_meta.ts +++ b/website/pages/docs/_meta.ts @@ -17,7 +17,7 @@ const meta = { title: 'Advanced Guides', }, 'constructing-types': '', - nullability: '', + 'nullability': '', 'abstract-types': '', 'oneof-input-objects': '', 'defer-stream': '', From 4bc1b86b617f0fd0967c9492db0a93254601b6db Mon Sep 17 00:00:00 2001 From: sarahxsanders Date: Tue, 27 May 2025 17:12:18 -0400 Subject: [PATCH 4/4] fix prettier error --- website/pages/docs/_meta.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/website/pages/docs/_meta.ts b/website/pages/docs/_meta.ts index 8cf078bdfa..998747d411 100644 --- a/website/pages/docs/_meta.ts +++ b/website/pages/docs/_meta.ts @@ -17,7 +17,7 @@ const meta = { title: 'Advanced Guides', }, 'constructing-types': '', - 'nullability': '', + nullability: '', 'abstract-types': '', 'oneof-input-objects': '', 'defer-stream': '',