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
SearchResult
union can be either aBook
or anAuthor
. - The
search
query returns aSearchResult
, which means it can return either aBook
or 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 Book
and... 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
search
resolver returns either aBook
or anAuthor
based on the query. - The
__resolveType
function determines the type of the returned object by checking its properties.
Practical Exercise
Exercise
- Define a union type
Media
that can be either aMovie
or aTVShow
. - Create a query
mediaSearch
that returns aMedia
. - Implement the resolver for
mediaSearch
and 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.