Handling Different Response Formats with Union Types in GraphQL


Union types are a way to define a field that can return one of a list of types. For example, when you're building a GraphQL API that has a query to search for publications. These publications can be books, magazines, or other types of publications. But what if every publication type has a different set of fields?
This blog post will look at how to use union types in GraphQL to make your schema more flexible. We'll also look at how to use union types in StepZen, a GraphQL API solution that allows you to query multiple APIs with a single GraphQL endpoint.
Using Union Types in a GraphQL schema
With union types, you can make your schema more flexible. Let's look at the following schema, where we have a SearchResult
type that can return either a Book
or a Magazine
type:
type Book {
title: String!
authors: [String]!
}
type Magazine {
title: String!
}
union SearchResult = Book | Magazine
In this example, the SearchResult
type can return either a Book
or a Magazine
type. This is useful when we want to return a list of publications that can be either a book or a magazine. Especially as the Book
type has an authors
field, but the Magazine
type doesn't.
The query that resolved to the SearchResult
type can be defined as follows:
type Query {
search(query: String!): [SearchResult]!
}
If we didn't use a union type, we would need to define a separate query for each publication type, resulting in a lot of duplication in our schema.
Querying Union Types
When you send a request to the GraphQL endpoint, you need to define which fields you want to be returned in case the response is of either type you want to query. GraphQL uses inline fragments to specify which fields you want to query for each type.
For example, if we want to query the title
and authors
fields of a Book
and only the title
field of a Magazine
, we can define the query as follows:
query search {
search(query: "webdevelopment") {
... on Book {
title
authors
}
... on Magazine {
title
}
}
}
The GraphQL server will return a list of publications that match the search term "webdevelopment". If the publication is a book, the server will return the title
and authors
fields. The server will return the title
field if the publication is a magazine.
Using Union Types in StepZen
When you're building a GraphQL API with StepZen, you can, of course, also use union types. Let's create a new StepZen endpoint that will query the Google Books API. The Google Books API is a public API that allows us to search books and magazines.
Create a StepZen endpoint
To create a new StepZen endpoint, we need to install the StepZen CLI. The StepZen CLI is a command line tool that allows us to create, deploy, and manage StepZen endpoints.
To install the StepZen CLI, we can run:
npm install -g stepzen
After installing the StepZen CLI, we can create a new directory for our project. In this directory, we can initiate a new StepZen project by running the following:
stepzen init --endpoint=api/googlebooks
The CLI will create a new file called stepzen.config.json
, which contains the configuration for our project, such as the endpoint name.
Next, we need to create a schema file. This file will contain the schema for the Google Books API. We can generate this schema file by running the following:
stepzen import curl 'https://www.googleapis.com/books/v1/volumes?q=webdevelopment&printType=magazines&country=US' --name=googlebooks --query-name=search --query-type=SearchResults
The generated schema is a valid GraphQL schema describing the data we can query from the Google Books API. The schema includes a query called search
that returns a list of publications based on a search term (q
). Also, you can filter by country (country
) and the type of publication (printType
). The printType
argument can be either books
or magazines
.
To deploy the schema as a StepZen endpoint, we can run:
stepzen start
This will deploy the schema to StepZen and return the endpoint it's available on in your terminal. Also, you get a link to explore the endpoint in the StepZen dashboard, as you can see in the next step.
Explore the Google Books API
We can explore the API by visiting the Explorer in the StepZen dashboard at https://dashboard.stepzen.com/explorer. The Explorer allows us to explore the GraphQL API by sending requests and viewing the schema.
Let's try sending a request to get the response for the search
query, which returns a list of books and magazines. We can send a request by clicking the "Run" button, and the Explorer will send a request to the StepZen endpoint and display the results.
query search {
search(
country: "US",
printType: "magazines",
q: "webdevelopment"
) {
items {
volumeInfo {
title
publishedDate
}
}
}
}
This query returns a list of magazines that match the search term "webdevelopment". Instead of finding a list of magazines, we can use the same Google Books API endpoint to retrieve a list of books. This is because the search
query allows us to change the argument printType
to books
to retrieve a list of books. By looking at the Google Books documentation, we can see that when the printType
argument is books
we can retrieve additional fields such as authors
and industryIdentifiers
-- which contain the ISBN of the book.
However, when we run the query with the printType
argument set to books
and request these additional fields, we get an error:
The Google Books API returns different data for magazines than for books, so we can't query the same fields for both types. To fix this, we can use a union type to define which fields are available for each type.
Generate a schema with a union type
Before we can add the union type, we need to generate a new schema file that includes the response types for books. We can do this by running:
stepzen import curl 'https://www.googleapis.com/books/v1/volumes?q=webdevelopment&printType=books&country=US' --name=books_googlebooks --query-name=searchbooks --query-type=SearchResults --prefix=Books
The Books
prefix is used to avoid naming conflicts with other types in the GraphQLschema, as there is a huge overlap between the types for books and magazines.
With this new query and schema, we can query the searchbooks
query to get a list of publications, where the type BooksVolumeInfo
is used for the volumeInfo
field. This type contains the fields title
, authors
, and industryIdentifiers
.
To set up the union type, we need to alter the schema file that includes the search
query. We can do this by opening the googlebooks/index.graphql
file and adding the following code to it:
type MagazinesVolumeInfo {
allowAnonLogging: Boolean
canonicalVolumeLink: String
contentVersion: String
description: String
imageLinks: ImageLinks
infoLink: String
language: String
maturityRating: String
pageCount: Int
panelizationSummary: PanelizationSummary
previewLink: String
printType: String
publishedDate: Date
readingModes: ReadingModes
title: String
}
union VolumeInfo = MagazinesVolumeInfo | BooksVolumeInfo
In the above, we've renamed the VolumeInfo
type to MagazinesVolumeInfo
and created a new union type called VolumeInfo
. The union type contains the MagazinesVolumeInfo
type and the BooksVolumeInfo
type from the schema file that includes the searchbooks
query.
Explicitly define the type names for the union type
In a previous step, we've seen that the Google Books API returns different data for books and magazines. This means that the search
query can return a list of publications that are either books or magazines. As they return different fields, the union type VolumeInfo
resolves to either MagazinesVolumeInfo
or BooksVolumeInfo
.
Often, StepZen can determine which member of the union type the data belongs to based on the returned data. However, in this case, there are a few common fields between the two types. This means that StepZen can't easily determine the union type member based on the returned data.
This means that we need to explicitly set the __typename
field to determine the publication type for each item in the list.
To do this, we need to add the __typename
field to the search
query. We can do this by opening the googlebooks/index.graphql
file and editing the search
query to look like this:
type Query {
search(country: String, printType: String, q: String): SearchResults
@rest(
endpoint: "https://www.googleapis.com/books/v1/volumes"
ecmascript: """
function transformREST(s) {
let out = JSON.parse(s);
out.items = out.items.map((item) => {
let volumeInfo = item.volumeInfo;
volumeInfo.__typename = volumeInfo.printType === 'BOOK' ? 'BooksVolumeInfo' : 'MagazinesVolumeInfo';
return {
volumeInfo: volumeInfo
}
})
return JSON.stringify(out);
}
"""
)
}
To set the __typename
field, we use the ecmascript
directive to run some JavaScript code. In the code, we check the value of the printType
field and set the __typename
field to either BooksVolumeInfo
or MagazinesVolumeInfo
.
Query the Google Books API with a union type
Now that we've set up the union type, we can retrieve magazines and books from the Google Books API using the search
query.
From the StepZen dashboard, we can send a request with the following query:
query search {
search(
country: "US",
printType: "all",
q: "webdevelopment"
) {
items {
volumeInfo {
__typename
...on BooksVolumeInfo {
title
authors
industryIdentifiers {
type
identifier
}
}
...on MagazinesVolumeInfo {
title
}
}
}
}
}
Which results in the following response:
This query returns a list of magazines and books that match the search term "webdevelopment". The __typename
field is used to determine the publication type. If the type is BookVolumeInfo
, we can query the fields title
, authors
, and industryIdentifiers
. If the type is MagazineVolumeInfo
, we can query the field title
.
Conclusion
In this tutorial, we've learned about using union types in a GraphQL schema and how to use them in a request. We've also seen how to use StepZen to create a GraphQL API for the Google Books API by using the StepZen CLI to generate a schema file and the StepZen Explorer to send requests. Finally, we've seen how to use a union type to query the Google Books API for both books and magazines.
Want to learn more about StepZen? Try it out here or ask any question on the Discord here.