October 24th 2018 (22 days ago)

GraphQL interfaces and unions - how to design GraphQL schema

Author profile picture
David Mráz (@david_mraz1)

This article is a part of upcoming free GraphQL Language course. To fully understand the content you need to be familiar with basic GraphQL concepts like SDL language, GraphQL document syntax or GraphQL object types and scalars. If you do not have this knowledge, you can take a look at our older articles on graphqlmastery.com and on our medium publication.

Introduction

In the GraphQL specification we are able to use two abstract types:

  • interfaces
  • unions
In this article we will go through the use cases for abstract types and how we can implement them in our GraphQL schema. Using abstract types can greatly improve your GraphQL schema design and simplify your queries and mutations.

What is an interface?

Let's first define what is interface in programming. There are various definitions for interfaces, but I found out that the most useful one for understanding GraphQL interfaces is as follows:

The interface is a structure that enforces certain properties on the object or class that implements the corresponding interface
Interfaces are usually needed when we are looking to access certain group of objects that has to comply to properties defined by interface. With interface we are basically abstracting group of types. By this abstraction we can reason about them as one entity.

Interfaces in GraphQL

Let's first define our model schema in SDL, so that we can start to apply our interface example. We would like to cover the types Planet, Constellation, Galaxy and Star. The types in SDL language can look for example like this:

type Galaxy {
  id: ID!
  createdAt: DateTime!
  updatedAt: DateTime
  name: String
  planets: [Planet]
  stars: [Star]
}
type Star {
  id: ID!
  createdAt: DateTime!
  updatedAt: DateTime
  name: String
  class: StellarClassEnum
}
type Constellation {
  id: ID!
  createdAt: DateTime!
  updatedAt: DateTime
  name: String
  galaxies: [Galaxy]
}
type Planet {
  id: ID!
  createdAt: DateTime!
  updatedAt: DateTime
  name: String
  description: String
  planetType: PlanetTypeEnum
}

As we mentioned when applying interface we are basically adding additional "node" above our types, so that we are able to access them as one enity. Let's say that we would like to access all the types from our schema as one single entity called "Node". To visualize this let's take a look at the following graph

In GraphQL we need to define every interface with at least one field. For example, in Relay.js we use what is known as a Node interface. Node interface usually serves as an interface for every type, which has the id field. If you are not using Relay.js on your frontend, I would also recommend to use this pattern. In Relay.js the Node interface has just one field id and it looks like this

interface Node {
  id: ID!
}

Implementing our custom Node interface

In our schema we have modified the common structure of Node interface to have the timestamps createdAt and updatedAt. Now let's take a look at how the Node interface is applied in our schema using SDL language. We first need to define the interface itself.

interface Node {
  id: ID!
  createdAt: DateTime!
  updatedAt: DateTime
}

When defining an interface we need to specify the fields that all children of the interface need to have. Once the interface is defined we need to choose what types should implement this particular interface. The Node interface in our case is implemented by all mentioned types in our model schema example. The modified SDL schema then looks as follows

type Galaxy implements Node {
  id: ID!
  createdAt: DateTime!
  updatedAt: DateTime
  name: String
  planets: [Planet]
  stars: [Star]
}
type Star implements Node {
  id: ID!
  createdAt: DateTime!
  updatedAt: DateTime
  name: String
  class: StellarClassEnum
}
type Constellation implements Node {
  id: ID!
  createdAt: DateTime!
  updatedAt: DateTime
  name: String
  galaxies: [Galaxy]
}
type Planet implements Node {
  id: ID!
  createdAt: DateTime!
  updatedAt: DateTime
  name: String
  description: String
  planetType: PlanetTypeEnum
}

We can see that every type implemented by the interface also has the same fields in common id, createdAt and updatedAt. These fields need to be also explicitly written for the type. If that is not the case, the GraphQL schema will be invalid. We are not constrained by using just Node interface. We can abstract different groups of types in our schema, such as multiple types of users (usually known as an Actor interface). The greatest advantage to this is it allows us to access a group of types in one single logical entity. This leads to a much cleaner schema design, as well as reducing the complexity of frontend.

Reducing the schema complexity

Now let's look at an example in which we reduce the complexity of queries and mutations. In our schema we would like to fetch Constellation, Planet, Stars or Galaxy based on the id. One approach is to add the query with the argument id for each different type for example like this.

type Query {
  star(id: ID!): Star
  planet(id: ID!): Planet
  galaxy(id: ID!): Galaxy
  constellation(id: ID!): Constellation
}
As you can see the approach is not scalable. The better way is to add just one query, node, which will return the Node interface. This query can be applied to retrieve any type which implements the Node interface. In most schemas you should implement the Node interface in every object type with id as the idis used as a global identifier. We can define the “node query” in SDL as follows:

type Query {
  node("""
  The ID of the object
  """
  id: ID!): Node
}

We would like to fetch just one Planet with "node query". To be able to do that we first need to fetch the id for some Planetwhich we can use as an argument for the “node query”. We can obtainids of the Planets by executing for example this query.

query getPlanets {
  planets {
    nodes {
      id
      name
    }
  }
}

After executing this query we receive the list of planets objects with the id and name for each planet.

{
  "data": {
    "planets": {
      "nodes": [
        {
          "id": "UGxhbmV0OjUwMTA1MTc5MzQ2Nzg1NDg2MQ==",
          "name": "Mars"
        },
        {
          "id": "UGxhbmV0OjUwMTA1MTc5MzQ2Nzg1NDg2MA==",
          "name": "Earth"
        }
      ]
   }
 }
}

We can then copy one of the ids and use the Node interface to fetch more detailed info about the planet with this query

query getPlanet($id: ID!) {
  node(id: $id) {
    ... on Planet {
      id
      name
      description
      createdAt
      updatedAt
    }
  }
}

with the following variables

{
  "id": "UGxhbmV0OjUwMTA1MTc5MzQ2Nzg1NDg2MQ=="
}

Then you will receive the full detail of the planet

{
 "data": {
    "node" {
      "id": "UGxhbmV0OjUwMTA1MTc5MzQ2Nzg1NDg2MQ==",
      "name": "Mars",
      "description": "Mars is the fourth planet from the Sun and the second-smallest planet in the Solar System after Mercury. In English, Mars carries a name of the Roman god of war, and is often referred to as the Red Planet.",
      "createdAt": "2018-10-14T15:20:56.668Z",
      "updatedAt": null
    }
  }
}

This is the example that will be used a lot whenever you want to make a frontend for GraphQL-driven apps. We can have for example a table of planets, but we do not want to fetch the whole planet instantly as it would be too expensive performance-wise and we will not use all the planet fields. We can render the name of the planets and, once we need more details, we can fetch the planetdetails with the “node query”. In our GraphQL document above we have introduced a new feature in GraphQL called “fragments”. For more info on fragments you can take a look at our article. Fragments help us tell the GraphQL server which fields we would like to retrieve for each type implemented by the interface. Now let’s check out the second abstract type called union.

GraphQL union

In the section above we went through the interface type. But what if we want to apply the similar abstraction to types that do not have any fields in common? For the Node interface example we assumed that every type which implements an interface contains the fields id, createdAt and updatedAt. But let's consider two types which has no fields. We can think about these types as a special kind of interface, where “interface” has no field and therefore does not enforce any properties. The union type is great if you do not want to constrain yourself to validating children types. We just want to represent multiple types that do not need to have any fields in common. Let's say we want to implement a query for searching within our schema. However, there is the constraint that we can only search for Planetand Galaxy. We want to abstract these two types and access them as one "searchable" entity without enforcing any other properties on them. This is a great use case for using Union type. Let's take a look at how we would implement this search query in the SDL language:

union Searchable = Planet | Galaxy

To search with the GraphQL document we again need to use inline fragments to determine on which type we would like to apply our selection set, just as we do for interfaces. The search query is not the only application for GraphQL unions. Let's take a look how we would define the Number union.

union Number = Int | Float
We can see that in this union application we have abstracted two scalar types: Integer and Float. In this case we can not use an interface, as scalar types do not have any fields in common. We need to apply a union. In general, I would recommend using unions if you want to express types as a group that has certain common attributes which cannot be defined and enforced by an interface.

Summary

Interfaces and unions are the only available abstract types in the GraphQL specification. You can leverage them to reduce complexity of your schema, reduce the number of queries and mutations, and describe your data in a much more precise way. You might also ask yourself why we need two different abstract types. Why can’t the Union type just be an interface without any enforcing properties? Other people have asked this question as well. You can learn more about this topic in the following link. Please also note that all abstract types in GraphQL can be implemented only by output types. I hope that this article was useful to you and helps you to understand abstract types in GraphQL better.

Feel free to send any questions and feedback on this article or GraphQL Mastery in general to david@atheros.ai. You can also check out our upcoming free GraphQL language course or subscribe for new articles on GraphQL Mastery with the form below.

Join our newsletter to get notified about new articles and get instant access to our upcoming free GraphQL Course