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:
1union 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:
1type Query {
2 allMedia: [Media] # This list can include both Book and Movie objects
3}
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
:
1union SearchResult = Book | Author
2
3type Book {
4 title: String!
5}
6
7type Author {
8 name: String!
9}
10
11type Query {
12 search(contains: String): [SearchResult!]
13}
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:
1query GetSearchResults {
2 search(contains: "Shakespeare") {
3 # Querying for __typename is almost always recommended,
4 # but it's even more important when querying a field that
5 # might return one of multiple types.
6 __typename
7 ... on Book {
8 title
9 }
10 ... on Author {
11 name
12 }
13 }
14}
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:
1{
2 "data": {
3 "search": [
4 {
5 "__typename": "Book",
6 "title": "The Complete Works of William Shakespeare"
7 },
8 {
9 "__typename": "Author",
10 "name": "William Shakespeare"
11 }
12 ]
13 }
14}
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:
1const resolvers = {
2 SearchResult: {
3 __resolveType(obj, context, info){
4 // Only Author has a name field
5 if(obj.name){
6 return 'Author';
7 }
8 // Only Book has a title field
9 if(obj.title){
10 return 'Book';
11 }
12 return null; // GraphQLError is thrown
13 },
14 },
15 Query: {
16 search: () => { ... }
17 },
18};
19
20const server = new ApolloServer({
21 typeDefs,
22 resolvers,
23 csrfPrevention: true,
24 cache: 'bounded',
25 plugins: [
26 ApolloServerPluginLandingPageLocalDefault({ embed: true }),
27 ],
28});
29
30server.listen().then(({ url }) => {
31 console.log(`🚀 Server ready at ${url}`)
32});
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:
1interface Book {
2 title: String!
3 author: Author!
4}
If an object type implements
an interface, it must include all of that interface's fields:
1type Textbook implements Book {
2 title: String! # Must be present
3 author: Author! # Must be present
4 courses: [Course!]!
5}
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:
1type Query {
2 books: [Book!] # Can include Textbook objects
3}
Example
The following schema defines a Book
interface, along with two object types that implement it:
1interface Book {
2 title: String!
3 author: Author!
4}
5
6type Textbook implements Book {
7 title: String!
8 author: Author!
9 courses: [Course!]!
10}
11
12type ColoringBook implements Book {
13 title: String!
14 author: Author!
15 colors: [String!]!
16}
17
18type Query {
19 books: [Book!]!
20}
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:
1query GetBooks {
2 books {
3 title
4 author
5 }
6}
Clients can also query for subfields that aren't included in the interface:
1query GetBooks {
2 books {
3 # Querying for __typename is almost always recommended,
4 # but it's even more important when querying a field that
5 # might return one of multiple types.
6 __typename
7 title
8 ... on Textbook {
9 courses { # Only present in Textbook
10 name
11 }
12 }
13 ... on ColoringBook {
14 colors # Only present in ColoringBook
15 }
16 }
17}
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:
1{
2 "data": {
3 "books": [
4 {
5 "__typename": "Textbook",
6 "title": "Wheelock's Latin",
7 "courses": [
8 {
9 "name": "Latin I"
10 }
11 ]
12 },
13 {
14 "__typename": "ColoringBook",
15 "title": "Oops All Water",
16 "colors": [
17 "Blue"
18 ]
19 }
20 ]
21 }
22}
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:
1const resolvers = {
2 Book: {
3 __resolveType(book, context, info){
4 // Only Textbook has a courses field
5 if(book.courses){
6 return 'Textbook';
7 }
8 // Only ColoringBook has a colors field
9 if(book.colors){
10 return 'ColoringBook';
11 }
12 return null; // GraphQLError is thrown
13 },
14 },
15 Query: {
16 books: () => { ... }
17 },
18};