GraphQL with Ruby on Rails: The Big Picture
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)
æç« ćæ„çŒèĄšæŒ Mediumă