The Road to GraphQL

Facundo
Shogun Team
Published in
4 min readOct 20, 2020

Introduction

One of the most challenging topics for front enders is how to maintain complex states, especially data. In order to simplify our lives in Frontend, we opted for Apollo client, a library for accompanying developers while managing data fetching and caching. We’ll be paying attention to two of the library’s most essential features: declarative data fetching and developer experience.

Besides the power of GraphQL through Apollo Client, we also use Typescript, which helped us while scaling and maintaining our application.

First steps

At the beginning of the project, we started implementing some hooks to wrap the GraphQL queries. We used to have something like:

and this hook was being used by several pages

Here is where we realized we were doing things in an odd way. Why do we need to have a util function to build up our data coming from the backend? When backend changes, do we need to change our builder?

Introducing codegen

As you may know, backend provides a schema containing the types for the GraphQL fields. This is where the magic enters, and a generator can take advantage of the situation and generate Typescript typings for us.

The code generator is easy to install and customize. Just install:

yarn add graphql @graphql-codegen/cli

add to your scripts:

And lastly, just create a codegen.yml file where you are going to tell the generator what needs to be generated and from where it should pick the schema:

So, let’s say you have the following schema:

When you run the generator, you will get something like:

Now you can simply import the Data type into your codebase.

The code generator can also be customized in different ways using plugins. In our case we count with:

Here there is an example of our initial basic configuration:

Given the following schema:

We are going to take a look at what the generator provides to you.

First, some basic types:

And finally, we can add some GraphQL operations as well:

And the generator will give you back:

You can easily re-utilize across your app just by importing them.

Scaling GraphQL

As we started to grow our app, the number of pages and components grew as well. Having global queries brought us a new problem: performance and maintainability.

Performance

How did performance get affected by having global queries? Well, let’s say that you have the following query:

In certain scenarios, resolving the sections can be super expensive for the backend. If you only have this query for rendering an entire page, the user won’t be able to see any content until the whole sections are resolved. Why not just split the query into multiple ones? It may sound like it is an easy win, but we wanted to have a proper solution, entering Relay Containers.

The idea behind containers is pretty much the same as the concept of container vs. presentational components. Henceforth, our main effort would be about moving our global queries to collocated ones, so now we can have:

And once we run the code generator we will be able to use the generated operations:

The four components rendered by UserPage will trigger the requests to the backend in parallel. As you may expect, the sections will take some time to render, but the primary data will be rendered at the beginning without the need to wait for solving the entire data tree.

Maintainability

Coming back to the first example about fetching the entire tree of data from the userPage query, we have another problem: Most of the times we want to update the data (meaning that a mutation should be performed), how do we know which places need to be updated after a successful request?

One naive approach allowed by GraphQL is to use refetchQueries, a handy (but harmful) option:

An array or function that allows you to specify which queries you want to refetch after a mutation has occurred. Array values can either be queries (with optional variables) or just the string names of queries to be refeteched.

So why is this harmful? Because it will let you refetch just one entire query, and if we are still using global queries, it means that a whole data tree will be fetched.

Example:

Why will you need to refetch the whole suite of data in every different place across your app instead of what was updated? Entering: Fragments.

What if we could just build pieces of data that are going to be reused? And what about reusing them across collocated queries? Yes, this seems the way to go:

Now, instead of fetching the entire GetUserPageInfo query, we can update in the cache thanks to Apollo — the piece of data that we really need after an update.

Once the user page updates (considering users can only edit the main data), there won’t be any more need for fetching all the sections again. In the future, if users want to update sections, a new fragment will be added to this mutation — scalability for free.

Conclusion

GraphQL is a powerful tool that can help teams scale rapidly. When used in conjunction with other tools like Apollo client and Typescript, a developer’s day-to-day will be much more seamless going forward.

The most important lesson we’ve learned is to stop thinking in RestAPIs. In order to move forward with GraphQL properly, different Backend architectures that have been in use for a long time may need to be scrapped entirely.

Teams should take advantage of typing and proper React patterns such as containers vs. presentational components to make a product/service more successful in the long term.

Happy GraphQL!

--

--