2019-11-19
2391
#graphql
Laurin Quast
9858
Nov 19, 2019 ⋅ 8 min read

Handling GraphQL errors like a champ with unions and interfaces

Laurin Quast Software engineer.

Recent posts:

Exploring Nushell, A Rust Powered, Cross Platform Shell

Exploring Nushell, a Rust-powered, cross-platform shell

Nushell is a modern, performant, extensible shell built with Rust. Explore its pros, cons, and how to install and get started with it.

Oduah Chigozie
Apr 23, 2024 ⋅ 6 min read
Exploring Zed, A Newly Open Source Code Editor Written In Rust

Exploring Zed, an open source code editor written in Rust

The Zed code editor sets itself apart with its lightning-fast performance and cutting-edge collaborative features.

Nefe Emadamerho-Atori
Apr 22, 2024 ⋅ 7 min read
Implementing Infinite Scroll In Next Js With Server Actions

Implementing infinite scroll in Next.js with Server Actions

Infinite scrolling in Next.js no longer requires external libraries — Server Actions let us fetch initial data directly on the server.

Rahul Chhodde
Apr 19, 2024 ⋅ 10 min read
Integrating Django Templates With React For Dynamic Webpages

Integrating Django templates with React for dynamic webpages

Create a dynamic demo blog site using Django and React to demonstrate Django’s server-side functionalities and React’s interactive UI.

Kayode Adeniyi
Apr 18, 2024 ⋅ 7 min read
View all posts

7 Replies to "Handling GraphQL errors like a champ with unions and interfaces"

  1. great article! Question: If i’m returning a custom error from my graphql server do you know how can i return a `locations` array? does graphql ships with some helpers to do it? thanks

  2. I am a bit late to the party, but I have thought hard about your approach. For mutations, I think it is a nobrainer, much cleaner code and we need to define result objects for mutations anyway, so why not use a union of the “normal” return type plus all possible error states? I am not sold completetely on using it for regular queries, for the following reasons:

    – Things get more verbose, you have to add more code to queries. Fragments help here, of course.
    – Say you have a UserResult union that includes a normal User type plus various error types. Do you use that everywhere where a User type might show up? Do you use UserResult only for queries that might return errors and User for queries that do not? The first approach leads to UserResult pervading all of your code but it is consistent, even if you never expect an error. The second avoids that, but then an application performing a query has to be permanently aware wether to expect a User or UserResult object.
    – (this was the deal breaker for me) How do you treat errors when resolving scalar values? Unions cannot include scalar values, so the obvious approach of a union of String and a bunch of error objects is out (and it would make queries *really* verbose). A scalar is normally part of an object, so we could resolve the scalar when resolving the parent object and return a suitable error object if the scalar resolution fails. There are two drawbacks to this a) if resolution of the scalar fails, we error out on the whole object, we cannot return (partial) results from other fields. b) the field might not be part of the query anyway, in which case we resolve it needlessly. That leaves only one more approach, to define a container object for all scalars with a single field for the scalar value and combine it with a union of error objects. This again brings up the problems of verbosity (now *really, really* verbose) and consistency (do we use this *everywhere* for consistency? No “normal” scalars any more?, Or do we mix the two?). So, realistically we are back to null for error values and the standard error array, we have to look for errors in two places and we have to deal with “bubbling up” of nulls when a non null scalar errors out on resolution.

    I think this goes into the right direction but we really need a bit of support from the language here, something like support for a dedicated error object maybe?

    Any thoughts?

  3. @JL Thanks for your comment 🙂

    Regarding “Things get more verbose”:

    One could argue what is more verbose matching the error form the GraphQL error to the component where it occurs or switching over a union type.

    Every solution has its benefits and trade-offs. I usually use those errors for top level fields that resolve a single resource. E.g. for lists or trees where you know that it must exists you can use always use User instead of UserResult.

    Regarding: “How do you treat errors when resolving scalar values”

    This is also dependent on the use-case, why should a single scaler value fail in the first place? It is IMHO dependent on your business logic. Do you need to show an error for a scaler value that fails being displayed? Or could you get away with just returning null.

    I had a situation where I wanted to represent a state-machine. Depending on the state-machine state different properties should be accessible on the type.

    I ended up using a union type with multiple ObjectTypes that have the properties on them as they are available during a certain state. It also seemed a bit strange as during some stages there are no properties to query at all (despite __typename).

    If your business logic relies on a scaler value being unavailable, why not wrap it in a union type, it sounds a bit verbose but it does the job.

    In the end it I think it is a trade-off whether you want “type-safe” errors with a bit of a more verbose schema or error-guessing on the client.

    Regarding “I think this goes into the right direction but we really need a bit of support from the language here, something like support for a dedicated error object maybe?”

    Do you have a solution in mind? Write about it, prototype something and share it!

  4. Hi Laurin,

    “One could argue what is more verbose matching the error form the GraphQL error to the component where it occurs or switching over a union type”

    True, and I think the tradeoff in favor of unions is acceptable here.

    “I usually use those errors for top level fields that resolve a single resource. e. .g. for lists or trees where you know that it must exists you can use always use User instead of UserResult.”

    That is something I would avoid, one object, one result type so that you don’t have to think about which one to use.

    Re Scalar values, business logic and my thoughts in general: I was trying to think about an approach that works regardless of business logic and case by case requirements, i.e. a drop in replacement for the way errors are handled in GraphQL. So I thought about the (admittedly rare) case of resolving a scalar as well.

    “Do you have a solution in mind? Write about it, prototype something and share it!”

    I have been thinking about a special “error” object that is a subtype of all possible types, so that it can appear wherever we expect a scalar or object. It would be what “null” is now for error handling, but with more information about the error attached in place. That means we can have “null” back as a “normal” value and we do not have to deal with non-nullable fields and bubbling up of nulls. One way to go about this would be to use a (previously unused) reserved field name in the error object, like “__isError”. Need to think about this some more … 🙂

Leave a Reply