Skip to content

CONCEPT Cited by 1 source

GraphQL error propagation

Definition

GraphQL error propagation is the runtime semantic defined by the GraphQL specification: when a resolver throws an error on a non-nullable field, the error "bubbles up" the response tree until it reaches the nearest nullable ancestor, where it is replaced by null. The error is appended to response.errors; the data tree surfaces only as much of the partial result as is still valid.

This behaviour is a feature — partial success is first-class in GraphQL — but it interacts subtly with how errors are modelled.

The subtle interaction

Zalando's 2021 error-modeling post names this interaction as one of three axes in its classification framework:

"We also have to remember not to disrupt GraphQL semantics of error propagation. If an error occurs in one place in the query, it propagates upwards in the tree till the first nullable field occurs. This propagation does not happen with error types in Schema. It is essential to model these schema error types for only specific use-cases."

(Source: sources/2021-04-12-zalando-modeling-errors-in-graphql)

In other words:

  • An Error (in response.errors) propagates.
  • A Problem (a schema-modelled union branch) does not propagate — it's just which arm of the union the server returned.

Why this matters for design

Consider a product page that queries product(id: $id).

  • If we return the error as an Error in response.errors and Product is non-null, the field propagates to null, and the nearest nullable ancestor (say, the query root) sees null. The UI has exactly one place to check: "is data.product null?"
  • If we return the error as a ProductNotFoundProblem in union ProductResult = Product | ProductNotFoundProblem, every place in the schema that references a ProductResult — potentially many — must spread the union:
product(id: $id) {
  ... on Product { name price }
  ... on ProductNotFoundProblem { message }
}
collection(id: $bar) {
  ... on Collection {
    products {
      ... on Product { name }
      # ... on ProductNotFoundProblem { } ← required here too
    }
  }
}

This is the "hammer and nails" failure mode the post warns against. Using Problem types at every level destroys the UX of GraphQL.

The design rule it implies

Schema-level Problem types should appear only where the caller wants to branch explicitly — canonically at the mutation boundary, where a RegisterResult branching on RegisterSuccess | RegisterProblem maps directly to "submit succeeded" vs "show validation errors to user". At query leaves that aren't customer-actionable, use Errors and let propagation handle it.

Worked counter-example from the post

{
  product(id: "foo") {
    ... on ProductSuccess { success }
    ... on ProductError { error }
  }
  collection(id: "bar") {
    ... on CollectionSuccess {
      products {
        ... on ProductSuccess { success }
      }
    }
    ... on CollectionError { error }
  }
}

The post calls this "too much to type in a query and too many branches to handle in the code" — and this is a direct consequence of abandoning GraphQL's error-propagation semantics in favour of schema types for non-Customer-actionable failures.

Seen in

Last updated · 476 distilled / 1,218 read