Return an Array in GraphQL
GraphQL provides a query language to define the shape of data you'd like returned from an HTTP API on a server and a library to help make it happen. It's easy to return a single item or multiple items.
Query Many Items
To query in graphql, you setup what looks like a json payload without the values. For example, to get the id
and title
fields of all books in your API, you might write:
{
books {
id,
title
}
}
Query a Single Item
To query a single item, filtered by a unique id, you'd add a predicate to the books query:
{
books(id: 123) {
id,
title
}
}
Otherwise, everything remains the same.
The sameness or similarity with the single item query feels good from someone coming from a REST API, where resources similar to the above would be queried from URIs something like:
/books # multiple
/books/123 # single
But keeping the same query field, books
, for both queries will provide a challenge because we will have to support both the single and multiple query from the same place.
Define a Schema
On the server, you need to define a schema for possible supported shapes of data you can query. We currently have one field that we can query -- books
. In the case where we provide no filter (known as args in a schema), we want to return all books. If there is a filter, we want to use it, and return a single book. The schema might look like:
const { GraphQLID, GraphQLList, GraphQLObjectType, GraphQLSchema, GraphQLString } = require('graphql')
const bookType = new GraphQLObjectType({
name: 'book',
fields: {
id: { type: GraphQLID },
title: { type: GraphQLString }
}
})
const schema = new GraphQLSchema({
name: 'root',
fields: {
books: {
type: new GraphQLList(bookType), // <-- note type
args: {
id: { type: GraphQLID }
},
resolve(_, args) { // <-- the interesting part
return args.id
? repo.find(args.id)
: repo.findAll()
}
}
}
})
The decision to return a single item or multiple items happens in the resolve
function for books
. And the books
field config shows that the return type will always be a GraphQLList
of bookType
. This means that whether a single item or multiple items, an array will always be returned. This doesn't feel to terrible -- even familiar when comparing to things like JSON-API. More importantly, it's required for this return type to work.
At one point, I attempted to use GraphQLUnionType
to have two potential return types, but I got an error like:
books may only contain Object types, it cannot contain [the array type]
...meaning that I couldn't mix a single object type with an array type in the unioned return type.
So in our chosen, working solution above where we always return an array, any consuming client will query with thebooks
field. An array of one or many items will be returned. In the case of returning an array of a single item, the deserialization code will probably want to unwrap the item from the array and expose it as a single object, but obviously this is up to your preference.
There it is. Have you learned how to do this in a better or more elegant way?