You may have heard developers sing the praises of the wonders of GraphQL. In this series we like to learn technologies by using them, and this article will go through an example application that uses GraphQL.
What is GraphQL
GraphQL is a query language and runtime that can be used to build APIs. It holds a similar position in the development stack as a REST API but with more flexibility. Unlike REST, GraphQL allows response formats and content to be specified by the client. Just as SQL SELECT
statements allow query results to be specified, GraphQL allows returned JSON data structures to be specified. Following the SQL analogy, GraphQL does not provide a WHERE
clause but identifies fields on application objects that should provide the data for the response.
GraphQL, as the name suggests, models APIs as though the application is a graph of data. While this description may not be how you view your application, it is a model used in most systems. Data that can be represented by JSON is a graph since JSON is just a directed graph. Thinking about the application as presenting a graph model through the API will make GraphQL much easier to understand.
Using GraphQL in an Application
Now that we've described GraphQL in the abstract, let's get down to actually building an application that uses GraphQL by starting with a definition of the data model or the graph. Last year I picked up a new hobby. I'm learning to play the electric upright bass as well as learning about music in general, so a music-related example came to mind when coming up with a demo app.
The object types in the example are Artist and Song. Artists have multiple Song and a Song is associated with an Artist. Each object type has attributes such as a name
.
Define the API
GraphQL uses SDL (Schema Definition Language) which is sometimes referred to as "type system definition language" in the GraphQL specification. GraphQL types can, in theory, be defined in any language but the most common agnostic language is SDL so let's use SDL to define the API.
type Artist { name: String! songs: [Song] origin: [String] } type Song { name: String! artist: Artist duration: Int release: String }
An Artist has a name
that is a String
. The exclamation mark means the field is non-null
. songs
is an array of Song objects, and origin
which is a String
array. Song is similar but with one odd field. The release
field should be a time or date type but GraphQL doesn't have that type defined as a core type. For complete portability between different GraphQL implementations, a String
is used. The GraphQL implementation we will use does have the Time
type added so let's change the Song definition so that the release
field is a Time
type. The returned value will be a String
, but by setting the type to Time
we document the API more accurately.
release: Time
The last step is to describe how to get one or more of the objects. This is referred to as the root or, for queries, the query root. Our root will have just one field or method called artist
and will require an artist name
.
type Query { artist(name: String!): Artist }
Writing the Application
Let's look at how we would use this in an application. There are several implementations of a GraphQL server for Ruby. Some approaches require the SDL above to be translated into a Ruby equivalent. Agoo, an HTTP server I’ve built, uses the SDL definition as it is and the Ruby code is plain vanilla Ruby, so that is what we'll use.
Note that the Ruby classes match the GraphQL types. By having the Ruby class names match the GraphQL type names, we don't add any unneeded complexity.
Methods match fields in the Ruby classes. Note that the methods either have no arguments or args={}
. This is what GraphQL APIs expect and our implementation here follows suit. The initialize
methods are used to set up the data for the example, as we will see shortly.
The query root class also needs to be defined. Note the artist
method that matches the SDL Query
root type. An attr_reader
for artists
was also added. That would be exposed to the API simply by adding that field to the Query
type in the SDL document.
The GraphQL root (not to be confused with the query root) sits above the query root. GraphQL defines it to optionally have three fields. The Ruby class, in this case, implements on the query
field. The initializer loads up some data for an Indie band from New Zealand that I like to listen to.
The final set-up is implementation specific. Here, the server is initialized to include a handler for the /graphql
HTTP request path and then it is started.
The GraphQL implementation is then configured with the SDL defined earlier ($songs_sdl
) and then the application sleeps while the server processes requests.
Agoo::GraphQL.schema(Schema.new) { Agoo::GraphQL.load($songs_sdl) } sleep
The code for this example can be found on GitHub.
Using the API
To test the API, you can use a web browser, Postman or curl
.
The GraphQL query to try that looks like this:
{ artist(name:"Fazerdaze") { name songs{ name duration } } }
The query asks for the Artist named Fazerdaze
and returns the name
and songs
in a JSON document. For each Song the name
and duration
of the Song is returned in a JSON object. The output should look like this.
After getting rid of the optional whitespace in the query, an HTTP GET made with curl should return that content.
Try changing the query and replace duration
with release
and note the conversion of the Ruby Time to a JSON string.
Outro to the song
We enjoyed playing around with GraphQL and we hope you tagged along and learned something on the way. Thank you, you've been a great audience. If you want to talk more Ruby, we'll be at the bar selling merch.