Loading...
Why I Prefer GraphQL over REST for Building Web Applications
Having developed many web applications, many people have asked me why I use GraphQL instead of a REST API between the frontend and backend, even when the application is fairly simple. Isn't GraphQL overkill for simple applications? I believe it is not, but for a different reason than people might think.
Yes, I believe using GraphQL leads to having a much clearer API without dealing with HTTP methods and paths to define "resources" that don't always fit your use case and aren't considered REST. But that's only secondary.
The most important reason that I use GraphQL every time I build a web application is because of the architectural benefits it brings me, most notably: decoupling.
Decoupling
Essentially, what GraphQL allows me to do is to build the backend completely in isolation from the frontend. Other than a shared understanding of what data is necessary, the backend does not need to know anything about the frontend and how it will use the data.
Similarly, whenever the frontend needs data in a different shape or format than it has needed before, the data is often readily available and ready to be queried.
Relationships
Have you ever had a REST endpoint that would fetch a list of customers, for example, but the frontend needed the list of addresses associated with each customer as well? What did you do?
There are different ways of solving this problem. For instance, one could simply return a list of customers with an extra field called addresses
and add all the addresses to that field.
However, what if you don't need those addresses in a different scenario, and this field consumes a lot of resources?
These and other scenarios are often difficult to fit in a REST API. However, that's not all.
Whenever I provide a REST API to a domain model that does not fit perfectly with the expectations of that API, such as in the example above, I need to introduce "mappers".
Mappers take one or more objects and transform them into other objects. For instance, our customers controller may use a Customer service and an Address service to retrieve the data but stitches them together using a mapper. This adds yet another layer of complexity and room for leaky abstractions to our code!
How GraphQL solves these issues
Most GraphQL server implementations rely on so-called resolvers and data loaders. A good example of this is the DGS Framework by Netflix. When a client makes a query:
customer {
id
name
addresses {
id
street
city
}
}
The GraphQL server implementation will first call the resolver for the customer. It will then collect all the ids of the customers that have been retrieved within a certain time interval and send those ids to the addresses data loader. For instance, if customers 1, 2, and 3 have been loaded, then the addresses data loader may load the addresses for each of these customer ids at once.
Once the data is loaded, most GraphQL server implementations will then stitch all this information together, without additional interference from the developer.
This has a two major benefits:
- The data loaders will instantly solve the so-called N+1 problem (meaning that it will not load addresses for each customer separately)
- No need to create mappers to stitch addresses with customer data anymore: the GraphQL server implementation will do it for you. While you may still need mappers for other reasons, at least you won't need them for defining relationships between two different entities.
Data Resolution and Invalidation
Also, the frontend architecture becomes much cleaner due to the use of GraphQL.
Imagine you're building a frontend for the aforementioned customer. Whenever an address is updated or deleted, you may want to invalidate all other parts of your application where an address is shown.
For example, if you have a list of customers with an address, you also want those entities to be updated as well.
WireQuery uses the library urql instead of the de facto standard Apollo, for two reasons:
- urql is much simpler to use and easier to fully grasp than Apollo.
- urql easily allows you to invalidate and therefore automatically refetch entities of a certain type. Using urql you can easily state which entities a query relies on, and which entities are affected when performing a mutation.
The second reason is extremely powerful because this allows complete decoupling of components within your application.
A component that has nothing to do with another component, except that the other component updates an entity that the former component uses, will be automatically invalidated and correctly refreshed when the latter component is updating that entity.
While it may not speak to the imagination unless you've dealt with "component hell" as I like to call it, trust me: this is powerful! Just notice how interactive WireQuery is and how much Redux it uses: zero.
Note, however, that the same may be achieved without GraphQL (e.g. using SWR). It was just worth mentioning since it is very well implemented and easy to do in URQL.
Conclusion
While GraphQL may, or may not, have better esthetics than REST APIs, for me the real reason to use GraphQL is the fact that it allows for much better decoupling between several parts of my applications. For instance, the frontend can be fully decoupled from the backend and within the frontend, it's much easier to invalidate and refresh only the right set of data.
This approach is also heavily used within WireQuery. What you will see in the backend code is, for example, that "user roles" have a dependency on "users", but there's no dependency from "users" to "user roles". This means that if you would remove the "roles" package, the backend should run just fine but without user roles. Other techniques to achieve this are in play here as well, such as events, but GraphQL provides a big part of the puzzle there.
Loading...