Discriminated Unions in GraphQL and TypeScript

Both GraphQL and TypeScript support the concept of discriminated unions (also known as tagged unions). In this post, I’ll walk through setting up an example GraphQL schema and the corresponding TypeScript types, along with writing a query to retrieve a union type.

TypeScript Types

We’ll start with the TypeScript types. The documentation’s Advanced Types page has a “Discriminated Unions” section that uses the following set of types as an example:


interface Square {
  kind: "square";
  size: number;
}

interface Rectangle {
  kind: "rectangle";
  width: number;
  height: number;
}

interface Circle {
  kind: "circle";
  radius: number;
}

type Shape = Square | Rectangle | Circle;

GraphQL Schema

GraphQL also supports the concept of unions (see the Apollo documentation or the GraphQL documentation).

Here’s how we could represent our Shape type in a GraphQL schema:


type Square {
  kind: String!
  size: Float!
}

type Rectangle {
  kind: String!
  width: Float!
  height: Float!
}

type Circle {
  kind: String!
  radius: Float!
}

union Shape = Square | Rectangle | Circle

GraphQL Resolver

(I’ve been using Apollo Server for the GraphSQL backend, so this might be a bit Apollo-specific.)

In order to show the resolvers how to resolve the union type, we need to implement a __resolveType function in our Shape resolver that knows how to discriminate between the different shapes.



const resolvers = {
  Shape: {
    __resolveType(obj: { kind: string }, context, info) {
      if (obj.kind === 'square') {
        return 'Square';
      }

      if (obj.kind === 'rectangle') {
        return 'Rectangle';
      }

      if (obj.kind === 'circle') {
        return 'Circle';
      }

      return null;
    }
  }
}

Obviously, you could be more programmatic about converting the value in the kind field into the name of the GraphQL types, or you could implement this with a switch statement, etc. Since this is an example, I kept this as simple and straightforward as possible.

The Query

The way to query a union type in GraphQL is to specify which fields you want back for each possible type in the union. For example, here’s a GraphQL query that asks for a Shape, then specifies which fields to include for each kind of possible Shape. When doing this type of TypeScript integration, you will need to always include the kind field since that’s what the TypeScript code will use to discriminate between the different types.


query GetBestShape {
  shape {
    ... on Square {
      kind
      size
    }
    ... on Rectangle {
      kind
      width
      height
    }
    ... on Circle {
      kind
      radius
    }
  }
}

For this query, if the resolver for the GetBestShape query returns a Rectangle type, then the data sent back to the client will only contain the requested fields in the ... on Rectangle section. So if you request all of the Rectangle type’s fields, you can safely assign that to a variable with the TypeScript type of Rectangle.

That’s all there is to it.

Conversation
  • Mike says:

    since every square is also a rectangle how would you go about allowing for this? say you wanted to access the length and width properties on a square object could you somehow make that object have both the properties of square and rectangle?

    Or in a more real-world example, if you have an interface “person” and you have the types “engineer” and “manager” that inherit from it how would you solve the problem of people who are both engineers and managers?

  • Jahn says:

    Was really hoping this would go into checking __typename or each type within the union using resulting types. Typescript doesn’t seem to like that my union types have nullable fields in them, even when I’m checking __typename for success types explicitly. Maybe something to consider for future writing, as pretty much everything out there for using gql unions glosses over it.

  • Comments are closed.