academy

An instrumental intro to GraphQL with Ruby

Peter Ohler

Peter Ohler on

An instrumental intro to GraphQL with Ruby

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.

shell
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.

shell
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.

shell
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.

ruby
class Artist attr_reader :name attr_reader :songs attr_reader :origin def initialize(name, origin) @name = name @songs = [] @origin = origin end # Only used by the Song to add itself to the artist. def add_song(song) @songs << song end end class Song attr_reader :name # string attr_reader :artist # reference attr_reader :duration # integer attr_reader :release # time def initialize(name, artist, duration, release) @name = name @artist = artist @duration = duration @release = release artist.add_song(self) end end

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.

ruby
class Query attr_reader :artists def initialize(artists) @artists = artists end def artist(args={}) @artists[args['name']] end end

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.

ruby
class Schema attr_reader :query attr_reader :mutation attr_reader :subscription def initialize() # Set up some data for testing. artist = Artist.new('Fazerdaze', ['Morningside', 'Auckland', 'New Zealand']) Song.new('Jennifer', artist, 240, Time.utc(2017, 5, 5)) Song.new('Lucky Girl', artist, 170, Time.utc(2017, 5, 5)) Song.new('Friends', artist, 194, Time.utc(2017, 5, 5)) Song.new('Reel', artist, 193, Time.utc(2015, 11, 2)) @artists = {artist.name => artist} @query = Query.new(@artists) end end

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.

ruby
Agoo::Server.init(6464, 'root', thread_count: 1, graphql: '/graphql') Agoo::Server.start()

The GraphQL implementation is then configured with the SDL defined earlier ($songs_sdl) and then the application sleeps while the server processes requests.

shell
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:

shell
{ 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.

json
{ "data": { "artist": { "name": "Fazerdaze", "songs": [ { "name": "Jennifer", "duration": 240 }, { "name": "Lucky Girl", "duration": 170 }, { "name": "Friends", "duration": 194 }, { "name": "Reel", "duration": 193 } ] } } }

After getting rid of the optional whitespace in the query, an HTTP GET made with curl should return that content.

sh
curl -w "\n" 'localhost:6464/graphql?query=\{artist(name:"Fazerdaze")\{name,songs\{name,duration\}\}\}&indent=2'

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.

Peter Ohler

Peter Ohler

Guest author Peter Ohler creates quite a bit of high-performance code, and writes about it too, every now and then. He made the Agoo gem, which is a pretty cool high-performance HTTP server.

All articles by Peter Ohler

Become our next author!

Find out more

AppSignal monitors your apps

AppSignal provides insights for Ruby, Rails, Elixir, Phoenix, Node.js, Express and many other frameworks and libraries. We are located in beautiful Amsterdam. We love stroopwafels. If you do too, let us know. We might send you some!

Discover AppSignal
AppSignal monitors your apps