Chapter 6 GraphQL: Interfaces and Unions

6.1 Introduction

An interface exposes a certain set of fields that a type must include to implement the interface.

schema.graphql

interface Restaurant {
    id:ID!
    name: String!
}

type Indian implements Restaurant{
    id:ID!
    name: String!
    brewedBeer:Boolean!
}

type Burger implements Restaurant{
    id:ID!
    name: String!
    vegetarianOptions: Boolean!
}

type Query{
    listRestaurants: [Restaurant!]
}

Unions are identical to interfaces, except that they don’t define a common set of fields. Unions are generally preferred over interfaces when the possible types do not share a logical hierarchy.

union Item = Food | Electronic | Customer

type Electronic {
    size: Float
    weight: Float
}

type Food {
    family: String
}

type Customer {
    fullName: String
    zip: String
}
type Query{
    listItems: [Item!]
}
 

6.2 Fragments

Fragments are powerful technique when we are consuming a query that returns an Interface or an Union. They are used to define what attributes we want to obtain from the server depending on the type of the concrete element.

query {
    listRestaurants:{
        id
        name
        ... on Indian {
            brewedBeer
        }
        ... on Burger {
            vegetarianOptions
        }
        __typename
    }
}  

6.3 Code

Implement interfaces or unions in Go is not rock science.

First of all we need to re-generate code when the graphql schema has been modified.

make generate

graphql/model/models.go

We just need to add this empty functions

func (a *Indian) IsRestaurant() {}
func (a *Burger) IsRestaurant() {}

graphql/resolver/query.go

func (r *Query) ListResturants(ctx context.Context) ([]model.Restaurant, error) {
    var restaurants = make([]model.Restaurant, 0)
    indians, err := GetDB(ctx).ListIndians()
    if err != nil {
        return restaurants, err
    }
    for _, i := range indians {
        indian := &model.Indian{Indian: i}
        restaurants = append(restaurants, indian)
    }
    burgers, err := GetDB(ctx).ListBurgers()
    if err != nil {
        return restaurants, err
    }
    for _, b := range burgers {
        burger := &model.Burger{Burger: b}
        restaurants = append(burgers, burger)
    }
    return restaurants, nil
}

In regards to the code there’s not difference between implementing an interface or an union.

6.4 Challenges

  • Define an interface Person with commons attributes for Actor and Director. Add a new query listPeople that returns a list of people ([Person!]).
query{
    listPeople:[Person!]
}

Once you’ve implemented this query make use of fragments to return the below details

{
  "data": {
    "listPeople": [
      {
        "__typename": "Actor",
        "fullName": "Johnny Depp",
        "gender": "female"
      },
      ...
      {
          "__typename": "Director",
          "fullName": "Steven Spielberg",
          "country": "USA"
       }
       ...
    ]
  }
}
  • Define an union named Item that could be a Movie or an Actor. Add an operations listItems that return the full list of Items. [Item!]
query{
    listItems:[Item!]
}

Once you’ve implemented this query make use of fragments to return the below details

{
  "data": {
    "listItems": [
      {
        "__typename": "Movie",
        "title": "Edward Scissorhands"
      },
      ...
      {
        "__typename": "Actor",
        "fullName": "Russell Crowe"
      }
      ...
    ]
  }
}