GraphQL non-nullable fields can lead to cascading null errors.
Let's say this is the result of a GraphQL query:
{
"data": {
"user": {
"name": "user1",
"email": "user1@example.com",
"tickets": [{"id": "ticket1"}],
"project": {
"name": "Project1",
"description": "Description for project 1",
"client": "Client 1"
}
}
}
}
All the fields are marked as non-null:
type User {
name: String!
email: String!
project: Project!
tickets: [Ticket!]!
}
type Project {
name: String!
description: String!
client: String!
}
// + Ticket
Let's say the Project.client
is considered sensitive information and will be removed for non-admin users.
This is the result for the same query when the client
returns an error:
{
"data": null,
"errors": ...
}
One field was denied, but the whole User object is now null in the response. This is surprising but logical.
The Project.client
is marked as non-null, meaning that this field will always have a value. That means the User.project
has to be null.
But User.project
is marked as non-null, so it can't be null. Because of that, User
will be null.
The solution seems simple: make the Project.client
nullable as that is how it behaves. But that's a breaking change: clients don't need to check if that field is nullable or not because it's marked as required. By relaxing this requirement the clients now need to check the field before they use it. And introducing breaking changes is always painful.
While it seems desirable to avoid non-null fields in the schema, I'd still prefer them whenever it makes sense. If some users can get a field but not others then make it nullable. But if a field being null is a result of some backend-side error then mark it as non-nullable.
What I'd definitely advise against is to work around the nullability and keep the clients happy by introducing magic values or returning an empty list. A client
of "" or "INVALID" or "ACCESS DENIED" are terrible options.