Model Unions as a single type in GraphQL

Problem

The GraphQL API does not support Union types, yet you need query results that are comparable to using Unions.

This recipe is one solution to the problem outlined in Composable Types without Unions.

Solution

If the number of types is obvious, small, and not likely to change, then one option to model Union types is to be explicit about each relationship.

While this method can lead to some verbose queries, you are able to satisfy the objectives.

Setup

  1. Update your GraphQL schema to include:

    type User {
      name: String!
      favoriteMovies: [Movie]! @relation
      favoriteShows: [Show]! @relation
      favoriteBooks: [Book]! @relation
      # ...
    }
    
    type Movie {
      title: String!
      newRelease: Boolean
      # ...movie fields
    
      favoritedBy: [User]! @relation
    }
    
    type Show {
      title: String!
      newRelease: Boolean
      # ...TV show fields
    
      favoritedBy: [User]! @relation
    }
    
    type Book {
      title: String!
      newRelease: Boolean
      # ...book fields
    
      favoritedBy: [User]! @relation
    }
    
    type Query {
      allMovies: [Movie]!
      allShows: [Show]!
      allBooks: [Book]!
    
      moviesByNewRelease(newRelease: Boolean!): [Movie]!
      showsByNewRelease(newRelease: Boolean!): [Show]!
      booksByNewRelease(newRelease: Boolean!): [Book]!
    }

    This involves editing the GraphQL schema definition, wherever you have stored it, and then uploading the new schema in the Fauna Dashboard.

  2. Run GraphQL queries to create a User and some media items (Book, Movie, or Show):

    mutation {
      createUser(
        data: {
          name: "Alice"
          favoriteMovies: {
            create: [
              { title: "Black Widow", newRelease: true }
              { title: "Psycho" }
            ]
          }
          favoriteShows: {
            create: [
              { title: "The Expanse", newRelease: true }
            ]
          }
          favoriteBooks: {
            create: [
              { title: "Fellowship of the Ring" }
            ]
          }
        }
      ) {
        name
        _id
        favoriteMovies {
          data { title, newRelease, _id }
        }
        favoriteShows {
          data { title, newRelease, _id }
        }
        favoriteBooks {
          data { title, newRelease, _id }
        }
      }
    }

    The result should be similar to:

    {
      "data": {
        "createUser": {
          "name": "Alice",
          "_id": "332112150594060834",
          "favoriteMovies": {
            "data": [
              {
                "title": "Black Widow",
                "newRelease": true,
                "_id": "332112150596157986"
              },
              {
                "title": "Psycho",
                "newRelease": null,
                "_id": "332112150607692322"
              }
            ]
          },
          "favoriteShows": {
            "data": [
              {
                "title": "The Expanse",
                "newRelease": true,
                "_id": "332112150611886626"
              }
            ]
          },
          "favoriteBooks": {
            "data": [
              {
                "title": "Fellowship of the Ring",
                "newRelease": null,
                "_id": "332112150615032354"
              }
            ]
          }
        }
      }
    }
    The document IDs presented in the _id fields are different for every new document created in Fauna. The Objective 3 query requires two of the document IDs present in this result.

Objective 1: Query for all entities of a given type

Each entity has its own type, so they can be queried separately.

{
  allMovies {
    data { title, newRelease }
  }

  allShows {
    data { title, newRelease }
  }

  allBooks {
    data { title, newRelease }
  }
}

The result should be:

{
  "data": {
    "allMovies": {
      "data": [
        {
          "title": "Black Widow",
          "newRelease": true
        },
        {
          "title": "Psycho",
          "newRelease": null
        }
      ]
    },
    "allShows": {
      "data": [
        {
          "title": "The Expanse",
          "newRelease": true
        }
      ]
    },
    "allBooks": {
      "data": [
        {
          "title": "Fellowship of the Ring",
          "newRelease": null
        }
      ]
    }
  }
}

Objective 2: Query for all entities with certain properties

You can make multiple queries to get all of the results. Indexes are automatically added by the GraphQL API using the fields specified on the Query type to support search-by-field, such as the newRelease field:

{
  moviesByNewRelease(newRelease: true) {
    data { title, newRelease }
  }

  showsByNewRelease(newRelease: true) {
    data { title, newRelease }
  }

  booksByNewRelease(newRelease: true) {
    data { title, newRelease }
  }
}

The result should be:

{
  "data": {
    "moviesByNewRelease": {
      "data": [
        {
          "title": "Black Widow",
          "newRelease": true
        }
      ]
    },
    "showsByNewRelease": {
      "data": [
        {
          "title": "The Expanse",
          "newRelease": true
        }
      ]
    },
    "booksByNewRelease": {
      "data": []
    }
  }
}

Objective 3: Query for all relationships of an entity, regardless of which type it points to

  1. You can search for all user favorites by selecting each relationship field.

  2. You can search for all users that have the media item marked as a favorite, since each type has that field.

To make the following query work:

  • replace <USERID> with the document ID for the user created in the setup’s step 2 (just below the name field in the result).

  • replace <MOVIEID> with the document ID for the "Black Widow" movie created in the setup’s step 2.

{
  findUserByID(id: "<USERID>") {
    name
    favoriteMovies {
      data { title, newRelease }
    }
    favoriteShows {
      data { title, newRelease }
    }
    favoriteBooks {
      data { title, newRelease }
    }
  }

  findMovieByID(id: "<MOVIEID>") {
    title
    newRelease
    favoritedBy {
      data { name }
    }
  }
}

The result should be:

{
  "data": {
    "findUserByID": {
      "name": "Alice",
      "favoriteMovies": {
        "data": [
          {
            "title": "Black Widow",
            "newRelease": true
          },
          {
            "title": "Psycho",
            "newRelease": null
          }
        ]
      },
      "favoriteShows": {
        "data": [
          {
            "title": "The Expanse",
            "newRelease": true
          }
        ]
      },
      "favoriteBooks": {
        "data": [
          {
            "title": "Fellowship of the Ring",
            "newRelease": null
          }
        ]
      }
    },
    "findMovieByID": {
      "title": "Black Widow",
      "newRelease": true,
      "favoritedBy": {
        "data": [
          {
            "name": "Alice"
          }
        ]
      }
    }
  }
}

Is this article helpful? 

Tell Fauna how the article can be improved:
Visit Fauna's forums or email docs@fauna.com

Thank you for your feedback!