Skip to main content
Version: Next 🚧

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.

schema.graphql
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.

note

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.

caution

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​