CODE WARS: GraphQL - A New Hope
The Case for GraphQL over REST
GraphQL is an open-source data query and manipulation language for APIs, and a runtime for fulfilling queries with existing data. GraphQL was developed internally by Facebook in 2012 before being publicly released in 2015.
Main Argument
Imagine that you’re in your car. You’re driving, but instead of following the Road Traffic Act, you rely on something called “Road Traffic Recommendations Manual”. The latter doesn’t contain clear rules that everyone should follow, but guidelines on what’s recommended (but not compulsory) to do while driving. In a result everyone has the right to interpret the “Road Traffic Recommendations Manual” however they want. This may include: driving in whichever lane you want, starting and stopping the car whenever you want, as well as not giving signals to the other road users. If you think that this is a metaphor for the driving experience in Sofia, you’re wrong. This is just how I describe the REST communication.
Someone may correct me and say that REST has had many “transparent and good practices” for a long time, but they will always remain just that – “good practices”. In reality, no one knows how to structure a well-organized and easy-to-understand API. Every good programmer has his own view on good practices. For example, how should resource paths look like? What HTTP methods to use for CREATE, READ, UPDATE, DELETE operations? What should the Request and Response objects look like? All of these issues are vary depending on the project and team in question. Therefore, in this day and age, it is necessary to use technologies with clear rules and strict principles. For instance, a good alternative to REST is the technology GraphQL. Launched by Facebook in 2015, GraphQL solves most of the problems in the client-server communication.
Basic Concepts
THE BASIC CONCEPTS IN GRAPH QL
- Schema - schema is the basis for building the GraphQL API. It defines the rules by which the API will operate, and that includes what requests can be executed, what types will be expected, and what will be returned in response. Schema is the place where the whole design process, necessary to build a fully-featured and documented API, takes place.
Example:
type Query { author(id: Int!): AuthorType authors: [AuthorType] book(id: Int!): BookType books: [BookType] } type BookType { id: Int! name: String! author: AuthorType } type AuthorType { id: Int! name: String! } type Mutation { createBook(bookInput: BookInput!): BookType } input BookInput { name: String! authorId: Int! }
- Types – types are the way GraphQL achieves consistency. I’d like to pay special attention to the fact that there is an explicit way to mark nullable types. This leaves no chance for misinterpretation of what types to expect in the Request or Response objects.
- Validation – the combination of schema and strict types allows for a very good static validation and type introspection (the ability of different tools to read the schema and types for better visualization or "hints" when writing).
There are three types of actions in GraphQL:
- Query – the way that information is retrieved
- Mutation – the way changes in information are made
- Subscription – a way for real-time communication
What problems does GraphQL solve?
UNDER-FETCHING
In our daily work as programmers, we are faced with problems that are already known and solved. One of them is the so-called "Under-fetching" or the situation in which the API does not retrieve all the necessary information to complete your job. As a result, it is vital to make several, or even dozens of requests in order to collect all that is needed. To make the argument clear, let's consider the following:
If we have a data model that looks like this:
Book:
- Id
- Name
- AuthorId
- UserId
Author:
- Id
- Name
To get a Book object by an Id in REST, the query will look something like this:
Request - /api/Books/GetById?id=1Response - { "id": 1, "name": "Clean code" }
In GraphQL the same query will look like this:
Request - query { book(id: 1) { id, name } }Response - { "id": 1, "name": "Clean code" }
At first, the two queries may look the same, but the difference begin when we having more and more complex queries, such as loading additional fields from the Book object. In REST, this can be achieved by creating a new API or modifying an existing one. In GraphQL, this can be accomplished simply by modifying the query at the place in the code where needed:
Request - query { book(id: 1) { id, name, authorId, userId } }Response - { "id": 1, "name": "Clean code", "authorId": 1, "userId": 1 }
All other places, where requests are made, remain unchanged. This is the main advantage of using GraphQL – only the required information is used. Another example of Under-fetching is when we want to take several objects at the same time, such as Book and Author, in one query. In REST, this is once again a problem therefore a new API needs to be created, or two separate requests like this:
Request - /api/Books/GetById?id=1Response - { "id": 1, "name": "Clean code" }+Request - /api/Authors/GetById?id=1Response - { "id": 1, "name": "Uncle Bob" }
More complex queries, which extract all the information, can be made in GraphQL:
Request - query { book(id: 1) { id, name }, author(id: 1) { id, name } }Response - { book { "id": 1, "name": "Clean code" } , author { "id": 1, "name": "Uncle Bob" } }
OVER-FETCHING
The second major problem is so-called "Over-fetching", or a situation in which the API retrieves more information than needed. If there is an existing API that retrieves all of the information about the Book object, it is often misused:
Request - /api/Books/GetById?id=1Response - { "id": 1, "name": "Clean code", "authorId": 1, "userId": 1 }
Many different functionalities can be dependent on the same source. Any change in the source will require updating the way it is used everywhere. In GraphQL, this is avoided because each functionality defines its own needs:
Request - query { book(id: 1) { id, name, authorId, userId } }Response - { "id": 1, "name": "Clean code" }
or even:
Request - query { book(id: 1) { name } }Response - { "name": "Clean code" }
DOCUMENTATION
A third problem. And it is a big one. We can use the following table for easier comparison:
DESIGN
Fourth problem. This is a common problem about building an API that is easy to understand and convenient to use by everyone. In GraphQL there are two main approaches for building a schema. Their main advantages can be described as follows:
SDL-first
- Cross-team communication
- Fast implementation
- Fast mocking of API
- Better design
Code-first
- Fully driven by language
- Tool support
- Better consistency
There is a right time and place to use either of these approaches. It is very important to emphasize that they are not mutually exclusive. The SDL-first approach can very easily be used in group planning meetings by the frontend and backend teams, and then the backend team can use Code-first approach for the actual implementation.
Proper design consistency will be achieved, whatever approach is taken. If you ask 10 different teams that use REST about their design, you will get 10 different answers. This is not the case in GraphQL. Teams mostly adhere to one of the popular conventions imposed by industry leaders, such as GitHub, GitLab or Magento.
SUPPORTING MULTIPLE VERSIONS
Fifth problem. Every API gets trough many changes over time. These changes often lead to the need of supporting multiple structural and functional differences in the same API. This happens most often when the different clients of the same API cannot be updated at the same time as the server. This requires constant attention to the changes you make so that the already-existing implementation doesn’t stop working. There are ways REST to allow multiple versions of the client to use changing APIs without “breaking”. This is true in theory, but not in practice. The main problem is the type system of statically typed.