GraphQL with Ruby on Rails: The Big Picture

A working example on using GraphQL with Ruby on Rails

This is the first part in the GraphQL with Ruby on Rails series.

As a server developer intern at PicCollage, I, quite happily, have been working on upgrading our codebase to match the latest version (1.9.4) of graphql-ruby library. This library, as the name suggests, is the Ruby implementation of GraphQL.

Part of our API uses GraphQL, and since I on-boarded I have been curious about (among other ten million things) how GraphQL really works, so much so that I wrote an article about it as an attempt to understand more. Now I officially have the right to work with GraphQL and read up on it, it is time to finally figure out how all the pieces come together.

After some exploring and stumbling, I realized (rather belatedly) I was missing the the bigger picture. I knew what a schema is and what types are, but how does GraphQL fit in the whole server structure? I could not answer that. It took me one week to re-figure that out and another week to be able to work properly with it.

Two Things To Keep In Mind

These are the two things that might be quite obvious but really helped me a lot when I was studying GraphQL.

It is not that different from REST.

One misleading idea about GraphQL is this: Graphql and REST (what has been the mainstream API design for decades) are very different paradigms. I believed that for a long time and it threw me into a mindset that my existing knowledge on the REST mechanisms cannot apply here, so everything became a blur of unknown to me when I tried to tackle GraphQL. But they are really not that different. If we take a few steps back and go through the steps in the server from receiving the request to sending back the response (which we will do in a moment), without worrying about the details, GraphQL would become almost intuitive.

GraphQL is the Controller.

If we try to think of GraphQL in the MVC (Model-View-Controller) diagram, it would be the role of a controller. GraphQL is responsible for parsing the request, interacting with the model and formatting the response to be sent back, just like what a controller does.

From Request to Response: The Overall Process

In this post I have briefly explained the steps a Ruby on Rails application processes requests. Briefly, the router is the gate keeper responsible for the despatch of incoming requests. The router parses the url and sends the request to a controller’s action. The controller then interact with the model and the view to gather necessary information, before packing everything up and send a response back.

We can think of the Rails router as the counter in a restaurant. When customer sends the order(url), the counter would send the order to different chefs (controllers). Chefs are responsible for collecting the ingredients (models), cook them up, and send them to the plate presenters (views) for the final brush up.

With GraphQL, instead of going through the Model-View-Controller diagram, when the Rails router has parsed the incoming requests, it sends the request to the GraphQL server instead of the controller. In our restaurant analogy it would be the counter sends the order to a different restaurant.

Inside the GraphQL server, the request goes through three phases: parse, validate and execute, and a JSON response is returned. Just like in a typical REST structure a response is returned by the controller (either in the html format or in JSON format).

Three Phases in GraphQL

As just mentioned, every query (request, but in GraphQL we call it query–because it IS basically a query) goes through three phases. First, the query is parsed into an an AST (Abstract Syntax Tree). Then the AST is validated against the schema (checking the query syntax and fields’ existence). Lastly, the runtime traverses down (up?) from the root of the tree, and with the help of resolvers, collects up the results and send back a response.

The Traversing of the Tree

When the runtime starts executing the query, the first thing it does is detecting which of the three operation types (query, mutation, subscription) the query belongs. Then following the query tree structure, the runtime one by one checks further into the leaves, until it hits the end. At each step, there is a resolver involved, who gathers the desired information for that specific field in the query. When the query is parsed through, and the results are collected, GraphQL packs up and sends back the response.

What Does a Valid Query Look Like

query {
  category(key: "summer") {
    name
  }
}

A query looks like this. Here, we first specify which of the three operation types this query is going to be. We can omit the query keyword here because this is the default. The rest simply translates as ‘Give me the name of the category whose key has the value “summer”’. This query will be used throughout the following sections.

Using the GraphQL-Ruby Gem

Like many well-designed and meticulous architectures, GraphQL Ruby–the Ruby implementation of GraphQL–compartmentalizes different functionalities into different classes.

The Schema File: Where it All Begins

If you have been searching around, you will know the schema provides you the information of the possible types and fields in a query. With this in mind, you may be a little bit bewildered when you see the content of GraphQL::Schema class.

# app/graphql/root_schema.rb
class RootSchema < GraphQL::Schema
  query QueryType
end

That’s it. PicCollage’s real schema is really no more complex than that. Instead of expecting to see all the possible types and fields combination in this file, it is better if we think of a schema file as an entry point in this map. We start from here, whose only line, query QueryType, tells us the class for our query operation type is QueryType. Let us see the QueryType class now.

QueryType: An Operation Type

# app/graphql/types/query_type.rb
class QueryType < GraphQL::Schema::Object
  description 'the root query'

  field :category, CategoryType, null: true do
    argument :name, String, required: false
    argument :key, String, required: false
  end

  def category(**args)
    QueryResolver.category(object, args, context)
  end
end

This is the simplified version of a QueryType class, with only one field and some essential elements.

First thing to note that a query is a type/object, just like you may have a user type, so it inherits class GraphQL::Schema::Object and is placed inside the /types folder. From there we see a declaration of field followed by some arguments plus a block.

The first argument :category is the field’s name, by which a query can call (like we just did). The second argument CategoryType is the return type of this field. We will soon see that this links us to another GraphQL object class. The third argument is an option hash, inside it we passed null: true, meaning we accept a null return value for this field.

Inside the block there are two arguments. These are the arguments to be provided with the query. In this case, you can choose to provide a name or a key of the category you are querying, both are of type String.

Below the field definition is the method with the name category. This is our resolver for the field. GraphQL automatically looks into the method that has the same name with the field to look for the values the query is asking. Here, it directs us to class QueryResolver with arguments object, args, and context. Note that object and context are implicitly provided by the GraphQL server, and args are the arguments specified in the field definition above (in this case it may be either :name:key or nothing at all).

We will dive deeper into resolvers in the following post. Let us look at the object type CategoryType now.

CategoryType: An Object Type

# app/graphql/types/category_type.rb

class CategoryType < GraphQL::Schema::Object
  field :key, String, null: true
  field :name, String, null: false
  field :icon_url, String, null: true
end

Again this is a simplified version. Here, we see all three fields in this CategoryType are of type String, one of the built-in scalar type. This means we have hit the leaves in this tree. After the fields are resolved (values are collected), GraphQL will send back the response.

Now we have officially walked through the essential parts of the GraphQL server. In the next post we will look (a lot) closer into the resolvers.

References (and Good Resources)

GraphQL Resolvers: Best Practices
medium.com
Shopify/graphql-design-tutorial
github.com

文章同步發表於 Medium


  • Find me at