REST or GraphQL?

Anne-Laure Chadeyras
8 min readMar 16, 2021

I have been a software engineer for a year now, and work daily with our API. As a learning exercise, I thought it would be interesting to go through the process of building a small API, because learning by doing is something I believe in.

When retraining to become a software engineer with Flatiron school, we were using / building REST APIs. At work, we use GraphQL and I really like it. So the first question I asked myself was: should I build a REST API or use GraphQL? This article is an attempt at figuring out which I should use based on the specific needs of the API I am building and on what the community says about each solution.

Similarities and differences

The concept of resource

In Rest, the core idea is the resource. Each is identified by a URL and you receive that resource by sending an HTTP request to that URL. The response will look something like that:

// GET /plants/1{
"commonName": "Carrot",
"latinName": "Daucus carota",
"family": "Apiacea",
"lifeCycle":
"sowingStartMonth": "March",
"sowingEndMonth": "June",
"daysToGermination": 20,
...
...
}

In REST, the shape of the resource and the way you fetch that resource are linked. In GraphQL however, these two concepts are completely separate. In your schema you might have a Plant type and a LifeCycle type.

type LifeCycle {
id: ID
sowingStartMonth: String
sowingEndMonth: String
daysToGermination: Integer
harvestingStartMonth: String
harvestingEndMonth: String
}
type Plant {
id: ID
commonName: String
latinName: String
family: String
lifeCycle: LifeCycle
}
type Companion {
id: ID
commonName: String
}

The above describes the data that is available but says nothing about how those objects would be fetched by a client. To be able to access a particular piece of data we need a Query type in our schema.

type Query {
plant(id: ID!): Plant
lifeCycle(id: ID!): LifeCycle
}

Using GraphQL to fetch the data we would have something like

// GET /graphql?query={ plant(id: 1) { commonName, latinName, family, lifeCycle { sowingStartMonth, sowingEndMonth, daysToGermination }{
"commonName": "Carrot",
"latinName": "Daucus carota",
"family": "Apiacea",
"lifeCycle": {
"sowingStartMonth": "March",
"sowingEndMonth": "June",
"daysToGermination": 20,
}
"companions": {
{ "commonName": "Allium" }
{ "commonName": "Chives" }
}
}

Although both REST and GraphQL have some concept of resource and identification for these resources, both can be fetched via an HTTP request and both will most often return JSON data, there are some key differences. The link between the shape of the data and how the data is fetched differs. In REST the shape and the size of the data is defined on the server-side. In GraphQL the server declares what resources are available, but the client decides on the shape of the data they need.

Routes vs Schema

An API needs to be predictable, and a description of what is available is crucial. A REST API is usually described as a list of endpoints

GET /plants/:id
GET /plants/:id/companion/:id
POST /plants
PATCH /plants/:id
DELETE /plants/:id

In GraphQL a schema is used instead of a series of endpoints

type Query {
plant(id: ID!): Plant
lifeCycle(id: ID!): LifeCycle
companion(id: ID!): Companion
}
type Mutation {
addCompanion(input: AddCompanionInput): Companion
}
type Plant { ... }
type LifeCycle { ... }
type Companion { ... }
input AddCompanionInput { ... }

Both REST APIs and GraphQL APIs have entry points to the data, one being a list of endpoints the other one being a list of fields on one of the initial types (Query vs Mutation) and both have a way to distinguish between a read request and a write request: HTTP verbs in one case, initial types in the other. But when it comes to fetching the data, there is a major difference. In GraphQL, you can traverse from the entry point and follow the relationships described in the schema, all in a single request, whereas in REST you will need to call multiple endpoints to get related data.

Route Handlers vs Resolvers

Now that we have covered the resources and the entry points to the data, we need to understand what happens when an API is called. Basically some code is executed on the server that received the request in order for that same server to send the appropriate response to the client.

Using Elixir, REST endpoints and route handler could look like this:

// endpointdefmodule ThePlantsRepo.Router do
use ThePlantsRepo, :router
scope "/", ThePlantsRepo do
pipe_through :browser
get "/plants", LeafyController, :index
end
// route handlerdefmodule ThePlantsRepo.LeafyController do
use ThePlantsRepo, :controller
def index(conn, _params) do
// some stuff happens here
end
end

The lifecycle of an HTTP request in a REST API server is as follows:

  • The server receives the request and retrieves the attached HTTP verb and URL path (GET and “/plants” in this case)
  • The API library matches the verb and URL path to the corresponding route handler
  • The function executes and returns a result
  • That result is serialized, the corresponding response code and headers are added and the whole thing is sent back to the client.

In a GraphQL API, for the same example, what happens when the API is called would look like:

// schemadefmodule ThePlantsRepo.Schema do
use Absinthe.Schema.Notation
query do
field :plants, type: :plant do
resolve(&ThePlantsRepo.LeafyResolver.get_all_plants/2)
end
end
end
// resolverdefmodule ThePlantsRepo.LeafyResolver do
def get_all_plants(_, _) do
// some stuff happens here
end
end

In GraphQL, instead of providing an HTTP verb and a URL, we provide a function that matches a specific field on a type. In our case the plants field on the query type. The corresponding query in the front end would look like:

query {
plants {
id
commonName
latinName
}
}

What happens when our GraphQL API receives a request:

  • The server receives the request and retrieves the GraphQL Query
  • The query is traversed and for each field, the corresponding resolver is called. In the case where the request includes nested data, the following resolver would also be called.
  • The function executes and returns a result
  • The GraphQL library and server attaches that result to a response that matches the shape of the query

An example query which leads to multiple resolvers calls would be as follows

// queryquery {
plant(id: 1) {
id
commonName
latinName
family
lifecycle(id: 1) {
id
sowingStartMonth
sowingEndMonth
daysToGermination
}
companions {
id
commonName
}
}
}
// schemadefmodule ThePlantsRepo.Schema do
use Absinthe.Schema.Notation
query do
field :plants, type: :plant do
resolve(&ThePlantsRepo.LeafyResolver.get_all_plants/2)
end
field
:plant, type: plant do
arg(:id, non_null(:id)
resolve(&ThePlantsRepo.LeafyResolver.get_plant/2)
end
field :life_cycle, type: :life_cycle do
arg(:id, non_null(:id)
resolver(&ThePlantsRepo.CycleResolver.get_cycle/2)
end
end
end

REST advantages and disadvantages

What are the benefits of a REST API?

Based on quite a lot of articles and opinions, one of the key advantages of a REST API is that it is highly and easily scalable. This is mostly due to the fact that the architecture decouples the client and the server, allowing developers to scale applications rather easily. In practice the server-side code can be changed without affecting how the client-side operates, and vice versa. Both

On top of that high scalability, REST also makes for flexible APIs. Because the data is not tied to methods, a REST API can handle various types of calls and return various data formats. This way, an API can be built to suit. specific user’s needs without compromising the needs of a wider range of users.

What are the limitations of a REST API?

Most web and mobile applications people use today require large datasets which combine nested resources. Accessing nested resources with a REST-based API is a major downside. Indeed, accessing nested resources requires multiple round trips, which can be costly in terms of performance, and lead to a less optimal user experience. Let’s look at an example. Imagine you want an API to send you data about a specific plant. Each plant has a lifecycle and each plant has one or more companion plants. Each of these would have its own endpoint in a REST API. This means that getting this information would require three API calls.

Another commonly highlighted downside is over/under-fetching. Because the client can only request data from specific endpoints, and each endpoints returns a set data structure. Because of this, clients cannot specify the exact data structure they need, and end up over or under-fetching:

  • Over-fetching happens when the clients receives more data than they actually need.
  • Under-fetching happens when the endpoint queried by the client doesn’t return enough data compared to their needs, and one or more extra round trips to the server are needed.

Neither are great. On one side the client receives more than they need and the server sent more than what was needed. On the other side the client needs to hit the API more than once to get the appropriate data. At scale it could potentially affect performance.

GraphQL advantages and disadvantages

What are the benefits of using GraphQL?

If you modify a part of an application’s user interface, there is a high probability that the data you need will change. Some of the data you were fetching so far won’t be needed anymore or you will need to get more data than for the previous UI. With a GraphQL API, it is really easy to change client side requirements, adding or removing field from the query. If the required data isn’t available, then the schema on the server side would need to be amended, but in my experience, it isn’t the case for most front-end changes.

With GraphQL, I believe front-end developers tend to have a pretty good knowledge of how the data is structured. This is because when fetching data, you need to specify which field you need, otherwise the server will send an empty response.

What are the limitations of GraphQL?

From what I ave read on the subject it seems that the most talked about GraphQL downside is the difficulty of network level caching, since GraphQL uses a single endpoints and doesn’t follow HTTP specifications for caching. The main advantage of network level caching is that it can reduce the traffic to a server or keep frequently accessed data close to the client.

It could also be noted that for a very basic API, GraphQL would tend to require more time and effort to setup — with types, resolvers, queries, schemas — than building a REST-based API.

Another often cited downside is error handling. The use of a single endpoint in GraphQL means that every server request will return a 200 code. This means that you have no way of knowing whether a request was successful or not. Although there are options to help with error handling it it less straightforward than in a REST API.

Since the client is able to specify exactly what they need in terms of the shape of the data, a GraphQL API may face performance issues if clients send gigantic queries including highly nested data.

Finally, scale can be an issue in GraphQL. In a lot of companies, a GraphQL API can either be a monolith or be synonym of a lot of duplication. This article explains the situation thoroughly and exposes some of the existing solutions.

After more reading and having a conversation with my coworkers, the conclusion was that for a GraphQL API and a JavaScript front-end, the Apollo client team has been working hard to provide alternative solutions. Examples would be for caching and adding some form of limit to the size of a request to the server.

--

--

Anne-Laure Chadeyras

Software Developer — yogi, swimmer, cyclist, gardener and scuba diver