15

I'm having a hard time understanding when to use GraphQLInterfaceType and GraphQLUnionType.

I've RTFMs:

Can anyone offer up a real world example when these would be useful to get it through my thick head?

Matt K
  • 4,314
  • 3
  • 19
  • 33

2 Answers2

32

Both are meant to help you design a schema with a heterogeneous set of types, and you could achieve the same functionality using both, but GraphQLInterfaceType is more suitable when the types are basically the same but some of the fields are different, and GraphQLUnionType when the types are totally different and have totally different fields.

Ultimately whether to use one or the other depending on your schema design.

For a real world example, let's say you have a list of blogs, but blogs using framework A use username and password as authentication, and blog using framework B use email and password. We design it with a GraphQLInterfaceType like this:

const BlogType = new GraphQLInterfaceType({
  name: 'Blog',
  fields: {
    url: { type: new GraphQLNonNull(GraphQLString) }
    password: { type: new GraphQLNonNull(GraphQLString) }
  },
  resolveType: resolveBlogType 
});

const BlogAType = new GraphQLObjectType({
  name: 'BlogA',
  interfaces: [Blog],
  fields: {
    url: { type: new GraphQLNonNull(GraphQLString) }
    username: { type: new GraphQLNonNull(GraphQLString) },
    password: { type: new GraphQLNonNull(GraphQLString) }
  }
});

const BlogBType = new GraphQLObjectType({
  name: 'BlogB',
  interfaces: [Blog],
  fields: {
    url: { type: new GraphQLNonNull(GraphQLString) }
    email: { type: new GraphQLNonNull(GraphQLString) },
    password: { type: new GraphQLNonNull(GraphQLString) }
  }
});

function resolveBlogType(value) {
  return value.username ? BlogAType : BlogBType;
}

When we create a new blog sending username, it will create a BlogA.

We can query like this:

query MyQuery {
   blogs: {
     url
     password
     ... on BlogA {
       email
     }
     ... on BlogB {
       username
     }
   }
}

Now let's get the same functionality but using GraphQLUnionType, because we prefer to use simply one type of blog, and 2 types of authentication methods:

const AuthAType = new GraphQLObjectType({
  name: 'AuthA',
  fields: {
    username: { type: new GraphQLNonNull(GraphQLString) },
    password: { type: new GraphQLNonNull(GraphQLString) }
  }
});

const AuthBType = new GraphQLObjectType({
  name: 'AuthB',
  fields: {
    email: { type: new GraphQLNonNull(GraphQLString) },
    password: { type: new GraphQLNonNull(GraphQLString) }
  }
});

const AuthType = new GraphQLUnionType({
  name: 'Auth',
  types: [AuthAType, AuthBType]
  resolveType: resolveAuthType
});

const BlogType = new GraphQLInterfaceType({
  name: 'Blog',
  fields: {
    url: { type: new GraphQLNonNull(GraphQLString) }
    auth: { type: AuthType }
  },

});

function resolveAuthType(value) {
  return value.username ? AuthAType : AuthBType;
}

We can query like this:

query MyQuery {
   blogs: {
     url
     auth {
       ... on AuthA {
         username
         password
       }
       ... on AuthB {
         email
         password
       }
     }
   }
}

As you can see in this example we achieve the same thing with the interface or the union, but one or the other might be more appropriate depending on your schema design.

For example, let's say you want to add a blog framework C that also use email and password. You would need to include another field to be able to differentiate it from blog framework B in our resolveBlogType function. Let's add the type field. In our Union example, since we only have access to the fields within the Union, you would to add type to the Union. If in the future we wanted to add another Union with same fields for multiple frameworks, we would need to add the type field there as well. Not so nice to have type duplicated multiple times in our schema. It could be a better idea to use a Interface, and have a single type field accessible at the resolveBlogType function by all the Objects using the Interface.

Jesús Carrera
  • 10,611
  • 4
  • 59
  • 55
  • when you query for `blogs` on the relay fragment what is this corresponding to graphql-wise? I see your graphql interface type is named `Blog` – maxwell Nov 28 '16 at 17:42
  • You would need to create the `blogs` query, which is not in the sample code – Jesús Carrera Nov 29 '16 at 08:50
  • I'm still not sure I understand – arcom Jul 31 '17 at 01:33
  • When using this example as is I'm getting the error: `...\node_modules\graphql\type\schema.js:295` `throw _iteratorError2;` `^` `TypeError: graphQLType.getFields is not a function` – Alexander Pravdin Sep 13 '18 at 02:32
  • 1
    @AlexanderPravdin it's not actually shown in the docs, but fields needs to be a function that returns an object, you can refer to the js portion of this very helpful video: https://www.youtube.com/watch?v=UBGzsb2UkeY – J Garcia Mar 29 '19 at 03:35
2

The sematic of GraphQLInterfaceType is like most program language's interface . and graphql add some more behaviors for it. like check if the derived class implement all the fields,dynamic resolving to derived instance.
The sematic of GraphQLUnionType is not a Union ,but something like OR.(a little bit like the flowtype's type check?)
A real world example is example in Relay => Relay's Node design .
GraphQLInterfaceType is completely unrelated to GraphQLUnionType.
I think maybe you was confused by this?

interface Node{
  id: string
}

type Dog implements Node{
  id: string
}
type Cat implements Node{
  id: string 
}
union Animal = Dog | Cat

type User{
  node: Node
  animal: Animal
}

If be confused by this, you should get some book of strong type language to read.(like C# or Java or something else. maybe you should have a look at Flow too, this usage Dog|Cat is a type restriction)

Messa
  • 21,202
  • 4
  • 52
  • 73
chen x
  • 43
  • 4