The GraphQL Way
Let's talk about GraphQL and how using it could help accelerate development of your app.
This post is part of a series of [GraphQL] posts, and here are the links for reference:
- [GraphQL Rails Post] => Connect Rails models as GraphQL nodes with no N+1 queries issue;
- [GraphQL Limited Post] => Apply filters, sorting and paginating on a GraphQL Rails server with no N+1 queries;
[GraphQL] is meant to be flexible, maintainable, powerful, performant, and more. In this post we'll discuss some high level gains of using GraphQL. In the following posts, we'll explore some implementation details to use GraphQL to its fullest potential.
I've been hearing about GraphQL for years, and I have always been very skeptical. It seemed too abstract when I first started, and every time I tried to browse an implementation there were N+1 query issues, or bad usage of the libraries, or some unnecessary abstractions that just made coding more complex that it should. After immersing myself in it my understanding has evolved and now I am very appreciative.
The History
GraphQL was officially published by Facebook in 2015 as a specification for API querying with an additional javascript implementation as a reference. That was the first time the public was able to see it and start using it.
But the development of this specification started years before, in 2012 when Facebook decided to build a new version of the Feeds API for their mobile app. The new implementation was so successful that the company decided to open-source the work. If you are interested, there's a [GraphQL Documentary] with a bit more of this story.
What is GraphQL
GraphQL is not a graph database as some might infer from the name; rather it's a query language specification for APIs. The spec was created to make API development very flexible, and it has been so successful that nowadays there are numerous implementations for several languages and incredibly tooling. This means that you can, and probably should, convert all your current APIs to use GraphQL.
Now we will look at the backend and frontend aspects of GraphQL.
Producing GraphQL Content
GraphQL was designed for backend developers to abstract their data on a graph structure and expose it, usually through an HTTP API. In other words, this is the Graph
part of GraphQL.
This means that as a backend developer, you will need to create GraphQL Types, which would ideally be a 1-to-1 mapping with your data modeling. In reality we face legacy data modeling and old terminology everywhere, which may force us to break from direct mapping of data models to types. If you are creating a new graph model, it could be a good opportunity to "fix" the naming and modeling at least on the API level, so go for it.
GraphQL Types will act as a decorator or a presenter layer of your app.
In Ruby a type would be something like:
# app/graphql/types/category_type.rb
class GraphQL::Types::CategoryType < GraphQL::Types::BaseObject
field :id, ID, null: false
field :name, String, null: false
field :products, [GraphQL::Types::ProductType], null: true
end
Cool, so that's a definition of a bunch of fields that belongs to a CategoryType
that we want to expose to our API. With this information we are able to build the whole graph. I'll talk about queries and mutations later on.
Consuming GraphQL Content
As a frontend developer, you would choose which part of the graph they need at that time, node by node and also field by field. You use a query language to fetch that, so that's the QL
part of GraphQL.
As a very simple example, let's query all categories and fetch only their id and name:
query {
categories {
id
name
}
}
So far it seems a simple syntax to understand.
I have two points to highlight when consuming GraphQL.
The first is that the server will use the schema definition and all types on all fields to generate a complete documentation of the graph, queries and mutations. Chrome plugins such as [Altair] allow you to inspect the documentation, and also interact with it, by copying the query to its editor and executing it. It also provides autocomplete and type checking. It's very productive.
The second point is if you use typescript there is tooling available to introspect and download a schema JSON file of your whole GraphQL schema, and to generate type files for you. If you use typescript you'll understand why this is really cool. Check [Apollo Tooling] for that.
The Query Type
There's a special type on your graph called here as QueryType
. This will hold all the possible root queries to your graph in the same way you'd expose a regular field on a regular type. In other words the fields of this special type will be the entry points of your API.
Here's an example of it in ruby again:
# app/graphql/query_type.rb
class GraphQL::QueryType < GraphQL::Schema::Object
field :categories, [GraphQL::Types::CategoryType], null: false
def categories
Category.all
end
end
In this example there's a single query entry point to your graph called categories
that will return an array of CategoryType
and its implementation is just Category.all
.
By convention, all queries are idempotent, so they should be executable without having any side effect on the data. A query to all categories won't change anything in the database, just retrieve all categories every time it has been called.
The Mutation Type
Similar to the QueryType
, the MutationType
is also a special type that lives on the root of your GraphQL Schema definition.
Contrary to queries, mutations are expected to have a side effect. For example, creating a new category on the database, or altering an existing category.
In Ruby here's how we'd create a mutation:
# app/graphql/mutation_type.rb
class GraphQL::MutationType < GraphQL::Types::BaseObject
field :create_category, mutation: GraphQL::Mutations::CreateCategoryMutation
end
And:
# app/graphql/mutations/create_category_mutation.rb
class GraphQL::Mutations::CreateCategoryMutation < GraphQL::Mutations::BaseMutation
null true
argument :name, String, required: true
field :category, GraphQL::Types::Category, null: true
def resolve(name:)
category = Category.create(name: name)
{ category: category }
end
end
Now that we have the basic concepts of the technology, let's talk about the benefits of using it.
The Benefits of Using GraphQL
1. Maintainability
When you expose a graph of nodes on the backend side, you need to specify each node and its relations with other nodes. And that's it. It's way easier to maintain this way instead of a whole combination of possible ways a data is fetched on a regular HTTP Restful API.
2. Flexibility
The client determines which node is returned, so they can easily shape the response into a way they are going to use on the frontend.
3. Server Resource Savings
By knowing what specific data is required, the server can save CPU, memory, database calls, etc. Ideally when you fetch categories, you fetch only categories. If you need products per category, then you may run a second query or a joined one to get both tables at the same time. There's no waste there.
4. Smaller Payloads
As the query specifies the desired fields, then the payload between server and client is possibly much smaller on average. This is a big deal chiefly when the client is on a mobile apps, native or web ones. Outside of big cities, mobile data is not as powerful as we want. Internet speed can also be unpredictable. Smaller payloads can help keep apps operational even when data availability is low.
5. Free Documentation
The documentation is auto generated by your graph so there's no extra cost to maintain it.
6. It's a Standard
GraphQL is so prevalent these days that it's a standard already. In the old days of regular JSON HTTP APIs, I've created my own patterns to follow to limit the response payload, or to organize queries in a way. And many other developers have created their own patterns as well. The thing is, two different patterns does not make a standard.
A broadly used specification defines a standard. This means you can change projects or companies and very quickly understand what's going on with the GraphQL API on the new codebase. You don't need to learn what naming conventions people are using on the project. It's always the same across GraphQL.
Are There Any Cons of using GraphQL?
Yes, of course, there are always some downsides.
As soon as you start to learn the basics, you already have to jump to learning data batching through data loaders. This adds something to the learning curve. I'd say that's a cheap price to pay for such benefits. If you don't implement data loaders, you'll face N+1 query problems that you really want to avoid. No matter how small your app is, if you leave N+1 queries they will bite you bad. N+1 is a no go for sure. We'll talk more data loaders in a following post.
The graph modeling also opens up the opportunity to fetch too much data at once. This should be discouraged, and you may want to take a look into some depth/amount limiters strategy. Server GraphQL libraries usually limiter options as configurations. In the Ruby library, check for max_depth
and max_complexity
.
Also, the spec is not very opinionated regarding some very common topics to APIs, such as authentication, authorization, pagination, and errors. They let the developers choose how to solve them. There are some best practices guides though, so if you encounter one topic and you are not very sure yet, do some research and check how other people have implemented already. Here's a nice [GraphQL Best Practices] guides as a start.
Conclusion
We've been using GraphQL more frequently in the last year or so, to great success. We've seen that starting to use it in a nice way is not that trivial, but it's not complicated at all. In an upcoming blog post I'll show how we have implemented a GraphQL server on Rails without any N+1 query issues in a simple and organized way.
Thanks for reading!
[GraphQL]: https://graphql.org/ [GraphQL Rails Post]: https://hashrocket.com/blog/posts/graphql-way-rails-edition [GraphQL Limited Post]: https://hashrocket.com/blog/posts/graphql-way-limited-edition [GraphQL Documentary]: https://www.youtube.com/watch?v=783ccP__No8 [GraphQL Best Practices]: https://www.graphql.com/guides/ [Altair]: https://chrome.google.com/webstore/detail/altair-graphql-client/flnheeellpciglgpaodhkhmapeljopja?hl=en [Apollo Tooling]: https://github.com/apollographql/apollo-tooling