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 (or a list of that 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 Book and Movie objects}
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 SearchResult
union type that can return either a Book
or an Author
:
union SearchResult = Book | Authortype Book {title: String!}type Author {name: String!}type Query {search(contains: String): [SearchResult!]}
The SearchResult
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") {# Querying for __typename is almost always recommended,# but it's even more important when querying a field that# might return one of multiple types.__typename... 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
).
Here's a valid result for the above query:
{"data": {"search": [{"__typename": "Book","title": "The Complete Works of William Shakespeare"},{"__typename": "Author","name": "William Shakespeare"}]}}
For more information, see Using fragments with unions and interfaces.
Resolving a union
Before reading this section, learn about resolvers.
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 is responsible for determining an object's corresponding GraphQL type and returning the name of that type as a string. It can use any logic to do so, such as:
- Checking for the presence or absence of fields that are unique to a particular type in the union
- Using
instanceof
, if the JavaScript object's type is related to its GraphQL object type
Here's a basic __resolveType
function for the SearchResult
union defined above:
const resolvers = {SearchResult: {__resolveType(obj, contextValue, info){// Only Author has a name fieldif(obj.name){return 'Author';}// Only Book has a title fieldif(obj.title){return 'Book';}return null; // GraphQLError is thrown},},Query: {search: () => { ... }},};const server = new ApolloServer({typeDefs,resolvers,});const { url } = await startStandaloneServer(server);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 presentauthor: Author! # Must be presentcourses: [Course!]!}
A field can have an interface (or a list of that interface) as its return type. In this case, it can return any object type that implements
that interface:
type Query {books: [Book!]! # Can include Textbook objects}
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: [String!]!}type Query {books: [Book!]!}
In this schema, Query.books
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 {books {titleauthor}}
Clients can also query for subfields that aren't included in the interface:
query GetBooks {books {# Querying for __typename is almost always recommended,# but it's even more important when querying a field that# might return one of multiple types.__typenametitle... on Textbook {courses {# Only present in Textbookname}}... on ColoringBook {colors # Only present in ColoringBook}}}
This query uses inline fragments to fetch a Book
's courses
(if it's a Textbook
) or its colors
(if it's a ColoringBook
).
Here's a valid result for the above query:
{"data": {"books": [{"__typename": "Textbook","title": "Wheelock's Latin","courses": [{"name": "Latin I"}]},{"__typename": "ColoringBook","title": "Oops All Water","colors": ["Blue"]}]}}
For more information, see Using fragments with unions and interfaces.
Resolving an interface
Before reading this section, learn about resolvers.
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, contextValue, info){// Only Textbook has a courses fieldif(book.courses){return 'Textbook';}// Only ColoringBook has a colors fieldif(book.colors){return 'ColoringBook';}return null; // GraphQLError is thrown},},Query: {books: () => { ... }},};