Semantic Nullability
Motivation​
One of GraphQL's strengths is its field-granular error handling which can dramatically improve response resiliency. However, today that error handling depends upon field nullability, which is the reason it is a recommended best practice to default all fields to being nullable. This creates a trade-off where enabling maximum resiliency means client developers must manually handle all possible permutations of field nullability within their components. @required
can help a bit, but is ultimately a very blunt tool.
Proposed Solution​
Semantic Nullability is an early GraphQL spec proposal that aims to decouple error handling and nullability in the GraphQL spec to enable maximum resiliency while still exposing the "semantic nullability", (the nullability of the actual resolver function/method on the server) of the field to the client.
The proposal works by allowing the schema to specify a new type of nullability of "null only on error". If a client sees this type, and the client has some strategy for handling field errors out-of-band, it may treat the field that is exposed to user code as non-nullable.
The full spec change will likely require adding additional syntax to GraphQL's schema definition language, but in the meantime, various GraphQL servers and clients have collaborated on a temporary directive @semanticNonNull
that can be used to experiment with this idea.
In short, you can add @semanticNonNull
to a field in your schema to indicate that the field is non-nullable in the semantic sense, but that the client should still be prepared to handle errors.
Relay will look for @semanticNonNull
directives in your schema and generate non-nullable Flow/TypeScript types for those fields if you enable client-side error handling for your query or fragment by adding the @throwOnFieldError
directive.
If your server will never return null
for a user's name, except in the case of errors, for example because it's resolver is typed as non-nullable, you can then apply @semanticNonNull
to that field in your schema.
directive @semanticNonNull(levels: [Int] = [0]) on FIELD_DEFINITION
type User {
name: String @semanticNonNull
}
Once you've added the directive to your schema, you can add @throwOnFieldError
to your fragment and queries to indicate that the client should throw an error if any field errors are encountered when the fragment is read.
Be sure to add React error boundaries to your app above any components that are using @throwOnFieldError
.
In the below example, Relay's generated TypeScript or Flow types for user.name
will be non-nullable.
If Relay receives a field error for user.name
, useFragment
will throw an error. For this reason, it's important to ensure that you are have adequate React error boundaries in place to catch these errors.
import type {UserComponent_user$key} from 'UserComponent_user.graphql';
const React = require('React');
const {graphql, useFragment} = require('react-relay');
type Props = {
user: UserComponent_user$key,
};
function UserComponent(props: Props) {
const user = useFragment(
graphql`
fragment UserComponent_user on User @throwOnFieldError {
name # Will be typed as non-nullable
}
`,
props.user,
);
return <div>{user.name}</div>
}
By Example​
For a hands on example, see this example project showing Relay configured to use @semanticNonNull
and @throwOnFieldError
alongside Grats which has support for automatically deriving a schema that includes the experimental @semanticNonNull
directives.
GraphQL Conf Talk​
The Relay team gave a talk at GraphQL Conf 2024 about semantic nullability. You can watch it here:
Further Reading​
- True Nullability Schema
- Strict Semantic Nullability
- RFC: SemanticNonNull type (null only on error)
- Grat's support/documentation for
@SemanticNonNull
- Apollo's specification for this directive
- Support for
@SemanticNonNull
in Apollo Kotlin added in 4.0.0-beta.3 - Awesome Semantic Nullability a list of frameworks and stand alone tools that support semantic nullability