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:

Solid Principles For Javascript

SOLID principles for JavaScript

SOLID principles help us keep code flexible. In this article, we’ll examine all of those principles and their implementation using JavaScript.

Frank Joseph
Dec 5, 2024 â‹… 10 min read
Master JavaScript Date And Time: From Moment.js To Temporal

Master JavaScript date and time: From Moment.js to Temporal

JavaScript’s Date API has many limitations. Explore alternative libraries like Moment.js, date-fns, and the new Temporal API.

Yan Sun
Dec 4, 2024 â‹… 9 min read
Npm Vs. Npx: What’s The Difference?

npm vs. npx: What’s the difference?

Explore use cases for using npm vs. npx such as long-term dependency management or temporary tasks and running packages on the fly.

Fimber Elemuwa
Dec 3, 2024 â‹… 5 min read
How To Audit And Validate AI-Generated Code Output

How to audit and validate AI-generated code output

Validating and auditing AI-generated code reduces code errors and ensures that code is compliant.

Boemo Mmopelwa
Dec 2, 2024 â‹… 5 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