Introduction to Unions
In GraphQL, unions are a powerful feature that allows you to define a type that can represent one of several different object types. This is particularly useful when you have a field that can return multiple types of objects, and you want to handle them in a type-safe manner.
Key Concepts
- Union Type: A union type is a special kind of type that can represent one of several object types.
- Type Safety: Unions help maintain type safety by ensuring that the field returns one of the specified types.
- Type Resolution: When querying a union type, the GraphQL server must resolve which type is being returned.
Defining Unions
To define a union in GraphQL, you use the union keyword followed by the name of the union and the types it can represent.
Example
Let's consider a scenario where we have a SearchResult union that can be either a Book or an Author.
# Define the Book type
type Book {
title: String!
author: Author!
}
# Define the Author type
type Author {
name: String!
books: [Book!]!
}
# Define the SearchResult union
union SearchResult = Book | Author
# Define a query that returns a SearchResult
type Query {
search(query: String!): SearchResult
}In this example:
- The
SearchResultunion can be either aBookor anAuthor. - The
searchquery returns aSearchResult, which means it can return either aBookor anAuthor.
Querying Unions
When querying a union type, you need to use inline fragments to specify the fields you want to retrieve for each possible type.
Example
query {
search(query: "GraphQL") {
... on Book {
title
author {
name
}
}
... on Author {
name
books {
title
}
}
}
}In this query:
- We use inline fragments (
... on Bookand... on Author) to specify the fields we want to retrieve for each type. - This ensures that we only request fields that are valid for the returned type.
Implementing Resolvers for Unions
When implementing resolvers for union types, you need to provide a type resolver function that determines which type is being returned.
Example
const resolvers = {
Query: {
search: (parent, args, context, info) => {
// Example search logic
if (args.query === "GraphQL") {
return { title: "Learning GraphQL", author: { name: "Eve Porcello" } };
} else {
return { name: "Eve Porcello", books: [{ title: "Learning GraphQL" }] };
}
},
},
SearchResult: {
__resolveType(obj, context, info) {
if (obj.title) {
return 'Book';
}
if (obj.name) {
return 'Author';
}
return null;
},
},
};In this example:
- The
searchresolver returns either aBookor anAuthorbased on the query. - The
__resolveTypefunction determines the type of the returned object by checking its properties.
Practical Exercise
Exercise
- Define a union type
Mediathat can be either aMovieor aTVShow. - Create a query
mediaSearchthat returns aMedia. - Implement the resolver for
mediaSearchand the type resolver forMedia.
Solution
# Define the Movie type
type Movie {
title: String!
director: String!
}
# Define the TVShow type
type TVShow {
title: String!
seasons: Int!
}
# Define the Media union
union Media = Movie | TVShow
# Define a query that returns a Media
type Query {
mediaSearch(query: String!): Media
}const resolvers = {
Query: {
mediaSearch: (parent, args, context, info) => {
// Example search logic
if (args.query === "Inception") {
return { title: "Inception", director: "Christopher Nolan" };
} else {
return { title: "Breaking Bad", seasons: 5 };
}
},
},
Media: {
__resolveType(obj, context, info) {
if (obj.director) {
return 'Movie';
}
if (obj.seasons) {
return 'TVShow';
}
return null;
},
},
};Common Mistakes and Tips
- Missing Type Resolver: Always provide a type resolver function for unions to ensure the server can determine the correct type.
- Invalid Fields: When querying unions, ensure that you only request fields that are valid for the returned type.
- Clear Type Distinction: Make sure the types in a union are distinct enough to be easily resolved.
Conclusion
Unions in GraphQL provide a flexible way to handle fields that can return multiple types. By defining unions and implementing type resolvers, you can maintain type safety and ensure your API is robust and easy to use. In the next section, we will explore another advanced schema design feature: Enums.
