Heading image for post: The GraphQL Way

The GraphQL Way

Profile picture of Vinicius Negrisolo

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!

More posts about graphql