Unions and interfaces
Abstract schema types
Unions and interfaces are abstract GraphQL types that enable a schema field to return one of multiple object types.
Union type
When you define a union type, you declare which object types are included in the union:
union Media = Book | Movie
A field can have a union as its return type. In this case, it can return any object type that's included in the union:
type Query {
allMedia: [Media] # This list can include both Books and Movies
}
All of a union's included types must be object types (not scalars, input types, etc.). Included types do not need to share any fields.
Example
The following schema defines a Result
union type that can return either a Book
or an Author
:
union Result = Book | Author
type Book {
title: String
}
type Author {
name: String
}
type Query {
search(contains: String): [Result]
}
The Result
union enables Query.search
to return a list that includes both Book
s and Author
s.
Querying a union
GraphQL clients don't know which object type a field will return if the field's return type is a union. To account for this, a query can include the subfields of multiple possible types.
Here's a valid query for the schema above:
query GetSearchResults {
search(contains: "Shakespeare") {
... on Book {
title
}
... on Author {
name
}
}
}
This query uses inline fragments to fetch a Result
's title
(if it's a Book
) or its name
(if it's an Author
).
For more information, see Using fragments with unions and interfaces.
Resolving a union
To fully resolve a union, Apollo Server needs to specify which of the union's types is being returned. To achieve this, you define a __resolveType
function for the union in your resolver map.
The __resolveType
function uses a returned object's fields to determine its type. It then returns the name of that type as a string.
Here's an example __resolveType
function for the Result
union defined above:
const resolvers = {
Result: {
__resolveType(obj, context, info){ if(obj.name){ return 'Author'; } if(obj.title){ return 'Book'; } return null; // GraphQLError is thrown }, },
Query: {
search: () => { ... }
},
};
const server = new ApolloServer({
typeDefs,
resolvers,
});
server.listen().then(({ url }) => {
console.log(`🚀 Server ready at ${url}`)
});
If a
__resolveType
function returns any value that isn't the name of a valid type, the associated operation produces a GraphQL error.
Interface type
An interface specifies a set of fields that multiple object types can include:
interface Book {
title: String
author: Author
}
If an object type implements
an interface, it must include all of that interface's fields:
type Textbook implements Book {
title: String # Must be present
author: Author # Must be present
courses: [Course]
}
A field can have an interface as its return type. In this case, it can return any object type that implements
that interface:
type Query {
schoolBooks: [Book] # Can include Textbooks
}
Example
The following schema defines a Book
interface, along with two object types that implement it:
interface Book {
title: String
author: Author
}
type Textbook implements Book {
title: String
author: Author
courses: [Course]
}
type ColoringBook implements Book {
title: String
author: Author
colors: [Color]
}
type Query {
schoolBooks: [Book]
}
In this schema, Query.schoolBooks
returns a list that can include both Textbook
s and ColoringBook
s.
Querying an interface
If a field's return type is an interface, clients can query that field for any subfields included in the interface:
query GetBooks {
schoolBooks {
title
author
}
}
Clients can also query for subfields that aren't included in the interface:
query GetBooks {
schoolBooks {
title # Always present (part of Book interface)
... on Textbook {
courses { # Only present in Textbook
name
}
}
... on ColoringBook {
colors { # Only present in ColoringBook
name
}
}
}
}
This query uses inline fragments to fetch a Book
's courses
(if it's a Textbook
) or its colors
(if it's a ColoringBook
).
For more information, see Using fragments with unions and interfaces.
Resolving an interface
As with union types, Apollo Server requires interfaces to define a __resolveType
function to determine which implementing object type is being returned.
Here's an example __resolveType
function for the Book
interface defined above:
const resolvers = {
Book: {
__resolveType(book, context, info){ if(book.courses){ return 'Textbook'; } if(book.colors){ return 'ColoringBook'; } return null; // GraphQLError is thrown }, },
Query: {
schoolBooks: () => { ... }
},
};