The API concept started some decades ago, and it is increasingly gaining popularity as time goes by until today. The acronym, which means Application Programming Interface, refers to an interface that intermediates between two or more applications. For example: A server connected to a database that delivers this data to a website asynchronously.
Since then, the API concept has had different protocols and architectures until the early 2000s when REST consolidated as the main way to build APIs in most languages. It continues to be used in different software around the world. For most of these years, we didn't have a popular alternative to REST until 2012 when Facebook decided to release a new technology in their API called GraphQL.
More than 10 years have passed, and this technology has grown in different use cases. However, there are many discussions about when and how to use GraphQL or REST. The answer will always be "It depends". That's why I decided to write this article explaining the proposed use of GraphQL over REST in an API.
How an REST API works?
Basically, a REST API uses the HTTP protocol for data transfer and manipulation in most cases. It functions similarly to a website when it comes to the concept of requests. In summary, a client (another application) makes a call to the server, which then responds with data or modifies it based on the type of method used. The server can also provide conditional data based on its API.
In a basic example, our client will be a website that displays a list of profiles on the main screen. Each profile will be represented by a clickable card, showing the user's name, photo, and state.
Clicking on the card, the client navigates to another page that displays profile details, such as the user's job position and description.
Well, this data is not located in our client but on our server. When requested, the server provides the data as a JSON object, like this:
{
"name": "Lucas Andrade",
"state": "Rio de Janeiro",
"description": "Tech. Music. Society. Software Developer at Geekie, co-founder at Criaway, learning to learn and teach.",
"employment": "Software Engineer"
}
Furthermore, we have another table in our database that contains user photos: one main photo and one secondary photo. Let's assume that the client needs to display the secondary photo on the first screen and the main photo on the details page. To fulfill this requirement, an additional request would be made, returning a JSON object like this:
{
"user": "1",
"primaryPicture": "firsturl.png",
"secondaryPicture": "secondurl.png"
}
For these two pages, our client will request the same server, which will provide this specific JSONs.
Although, this basic example exposes some problems, which GraphQL proposes to solve.
Overfetching
Basically, it occurs when a server provides unnecessary data that the client doesn't need. This results in increased resource usage on the server, leading to higher latency and expenses. In our example, the client only needs the user's name and state on the main page, while other information is required only on the details page. One solution for this example would be to create an additional endpoint, which would further increase the resource requirements.
Underfetching
On the other hand, underfetching occurs when there is not enough data retrieved with a single call to an endpoint, forcing you to make a second endpoint call. In our example, the client had to make two different requests for similar things (one to fetch users, another to fetch images). In addition to spending more resources, it also requires higher network performance.
Weak Typing
In an API written in languages like JavaScript or Python, we sometimes encounter a problem when type checking is skipped. For example, it becomes more cumbersome to verify if the field name
is provided as a string
, number
, null
, undefined
, or any other unexpected data type.
These are the problems, along with several others, that GraphQL aims to solve.
How a GraphQL API works?
For the beginning, when we talk about GraphQL, we no longer discuss distinct routes or endpoints. Each request is made to the same route, using the POST
method, and the details are provided in the request body, like this:
query getUser($where: UserInputWhere!, $pictureType: _PictureEnum) {
name
state
description
employment
pictures (pictureType: $pictureType)
}
Where:
-
Query
is the type of operation (in this case, the client is making a query to retrieve information). -
getUser
is the name of the operation, allowing the API to identify the specific action to perform. -
$id
and $where are variable names used in the query. For instance,id
is being passed as a variable, enabling the API to find the user based on the provided ID. -
name
,state
,description
,employment
, andpictures
are the fields returned by the API in response to the query.
To gain a basic understanding of how GraphQL works on the server side, I created a simple implementation:
- Firstly, we will define the
Input
, which will be used as an argument for the query. In our example, we will call itUserInputWhere
and it will have a strongly-typed and requiredID
(defined by"!"
):
input UserInputwhere {
id: ID!,
}
- Now, we define the
Type
that will be used for the query return. In this case, we will have the same fields as our REST API, but they will be strongly typed and required. Additionally, we will have a field called pictures, which will be a list of strings. I will explain how it works later:
type User {
name: String!
state: String!
description: String!
employment: String!
pictures (picture Type: _PictureEnum): [String]
}
- The pictures field will receive an argument, as a query inside another query. The argument is not required (indicated by the absence of "!") and has the type
_PictureEnum
. This type is used to restrict the options that can be passed and ensure that only values from the Enum options are accepted:
enum _PictureEnum {
PRIMARY
SECONDARY
ALL
NONE
}
- After that, we will define the query as
getUser
, withInput
beingUserInputWhere
and returning the typeUser
:
query getUser(where: UserInputwhere!): User
- Finally, we will define the resolvers. A resolver is a function that's responsible for populating the data for a single field in your schema. Your can read more about this in Apollo Docs:
const resolvers = {
Query: {
getUser: (root, args) => users.findOne({ _id: args.id }),
},
User: {
picture: (root, args) => resolverPicture(args.where),
}
}
A GraphQL advantage is the typing. If our client sends something like:
{ id: "invalid id" }
GraphQL will not accept this query because we don't have a valid ID
. This helps a lot in weakly typed languages. If our resolver returns a null
value in the name
field, for example, GraphQL will also throw an error.
Another strong GraphQL advantage is the Query. In the case of our client above, if we need another page just for searching the user's state
, I can only remove the other fields inside the object, like this:
query getUser($where: UserInputWhere!, $pictureType: PictureEnum) {
state
}
This way, the query will not return any fields that our client doesn't need, avoiding the overfetching mentioned before.
Furthermore, we have one of the biggest advantages of GraphQL. Do you remember the field pictures
defined above? We can use an intermediary function to find the images related to users according to the argument passed on pictureType
and return them all in the same query without needing different endpoints! When we don't need to get the images, we can just remove the field from the same object, avoiding another overfetching case and, in this case, saving up network bandwidth by making two database queries on the same endpoint!
In our client example, we can pass a { pictureType: PRIMARY }
on the first page and a { pictureType: SECONDARY }
on the second page! Or even a { pictureType: ALL }
in case we need all the photos.
To finish this example, we will create another query responsible for finding all users, reusing the majority of Types
and Inputs
, and returning an user required array:
query getUsers (where: Users Inputwhere!): [User]!
const resolvers = {
const resolvers = {
Query: {
getUser: (root, args) => users.findOne({ _id: args.id }),
getUsers: (root, _args) => users.find({}),
},
User: {
picture: (root, args) => resolverPicture(args.where),
}
}
GraphQL x REST
Briefly, GraphQL is recommended and can be an alternative in cases of:
- Applications with different client types where we need to build different implementations
- Applications that need a lot of nested data
- Applications where our API finds data from another different API, also with nested data
Conclusion
Even with its advantages, GraphQL definitely must not be used for situations with no difference or impact. Problems like overfetching or typing have different alternatives, and GraphQL is just one more. But an excellent alternative.
In this article, we explored how to implement an API in two ways, and we explain basically why GraphQL gained so much strength in the last few years and is a valid alternative.
Useful links
Top comments (0)