1. Overview
When creating web services to support our applications, we may choose to use REST or GraphQL as the pattern for communication. While both are most likely to use JSON over HTTP, they have different advantages and disadvantages.
In this tutorial, we’ll compare GraphQL with REST. We’ll create a product database example and compare how the two solutions vary when executing the same client operations.
2. Example Service
Our example service will allow us to:
- Create a product in draft status
- Update the product details
- Get the list of products
- Get details of a single product along with its orders
Let’s begin by creating the application using REST.
3. REST
REST (Representational State Transfer) is an architectural style for distributed hypermedia systems. The primary data element in REST is called Resource. In this example, the resource is “product”.
3.1. Create Product
To create a product, we’ll use a POST method on our API:
curl --request POST 'http://localhost:8081/product' \
--header 'Content-Type: application/json' \
--data '{
"name": "Watch",
"description": "Special Swiss Watch",
"status": "Draft",
"currency": "USD",
"price": null,
"imageUrls": null,
"videoUrls": null,
"stock": null,
"averageRating": null
}'
As a result, the system will create a new product.
3.2. Update Product
In REST, we conventionally update products using a PUT method:
curl --request PUT 'http://localhost:8081/product/{product-id}' \
--header 'Content-Type: application/json' \
--data '{
"name": "Watch",
"description": "Special Swiss Watch",
"status": "Draft",
"currency": "USD",
"price": 1200.0,
"imageUrls": [
"https://graphqlvsrest.com/imageurl/product-id"
],
"videoUrls": [
"https://graphqlvsrest.com/videourl/product-id"
],
"stock": 10,
"averageRating": 0.0
}'
As a result, there will be an update on the {product-id} object.
3.3. Get Product List
Listing products would normally be a GET operation, where we may use a query string to specify pagination:
curl --request GET 'http://localhost:8081/product?size=10&page=0'
The first response object is:
{
"id": 1,
"name": "T-Shirt",
"description": "Special beach T-Shirt",
"status": Published,
"currency": "USD",
"price": 30.0,
"imageUrls": ["https://graphqlvsrest.com/imageurl/1"],
"videoUrls": ["https://graphqlvsrest.com/videourl/1"],
"stock": 10,
"averageRating": 3.5
}
3.4. Get Single Product with Orders
To get a product and its order, we might normally expect to get a list of products with the previous API and then make calls to an orde**r resource to find related orders:
curl --request GET 'localhost:8081/order?product-id=1'
The first response object is:
{
"id": 1,
"productId": 1,
"customerId": "de68a771-2fcc-4e6b-a05d-e30a8dd0d756",
"status": "Delivered",
"address": "43-F 12th Street",
"creationDate": "Mon Jan 17 01:00:18 GST 2022"
}
As we’re querying for orders by product-id, it makes sense to supply the id as a query parameter to the GET operation. We should note, however, that we would need to execute this operation once for each product we were interested in, on top of the original operation to fetch all the products. This is related to the N + 1 Select Problem.
4. GraphQL
GraphQL is a query language for APIs that comes with a framework for fulfilling those queries using existing data services.
The building blocks of GraphQL are queries and mutations. A query is responsible for fetching data, while mutations are used for creation and update.
Both query and mutation define a schema. The schema defines the possible client requests and responses.
Let’s re-implement our example using GraphQL Server.
4.1. Create Product
Let’s use a mutation named saveProduct:
curl --request POST 'http://localhost:8081/graphql' \
--header 'Content-Type: application/json' \
--data \
'{
"query": "mutation {saveProduct (
product: {
name: \"Bed-Side Lamp\",
price: 24.0,
status: \"Draft\",
currency: \"USD\"
}){ id name currency price status}
}"
}'
In this saveProduct function, everything inside the round brackets is the input type schema. The curly brackets after this describe the fields to be returned by the server.
When we run the mutation, we should expect a response with the chosen fields:
{
"data": {
"saveProduct": {
"id": "12",
"name": "Bed-Side Lamp",
"currency": "USD",
"price": 24.0,
"status": "Draft"
}
}
}
This request is very similar to the POST request we made with the REST version, though we can now customize the response a little.
4.2. Update Product
Similarly, we can use another mutation named updateProduct to modify a product:
curl --request POST 'http://localhost:8081/graphql' \
--header 'Content-Type: application/json' \
--data \
'{"query": "mutation {updateProduct(
id: 11
product: {
price: 14.0,
status: \"Publish\"
}){ id name currency price status }
}","variables":{}}'
We receive the chosen fields in the response:
{
"data": {
"updateProduct": {
"id": "12",
"name": "Bed-Side Lamp",
"currency": "USD",
"price": 14.0,
"status": "Published"
}
}
}
As we can see, GraphQL provides flexibility over the format of responses.
4.3. Get Product List
To fetch the data from the server, we’ll make use of a query:
curl --request POST 'http://localhost:8081/graphql' \
--header 'Content-Type: application/json' \
--data \
'{
"query": "query {products(size:10,page:0){id name status}}"
}'
Here, we’ve also described the page of results we want to see:
{
"data": {
"products": [
{
"id": "1",
"name": "T-Shirt",
"status": "Published"
},
...
]
}
}
4.4. Get Single Product with Orders
With GraphQL, we can ask the GraphQL server to join together the products and the orders:
curl --request POST 'http://localhost:8081/graphql' \
--header 'Content-Type: application/json' \
--data \
'{
"query": "query {product(id:1){ id name orders{customerId address status creationDate}}}"
}'
In this query, we fetch the product with an id equal to 1 along with its orders. This enables us to make the request in a single operation, Let’s check the response:
{
"data": {
"product": {
"id": "1",
"name": "T-Shirt",
"orders": [
{
"customerId": "de68a771-2fcc-4e6b-a05d-e30a8dd0d756",
"status": "Delivered",
"address": "43-F 12th Street",
"creationDate": "Mon Jan 17 01:00:18 GST 2022"
},
...
]
}
}
}
As we can see here, the response has the product’s details and its respective orders.
To achieve this with REST, we were required to send a couple of requests – the first to get the product and the second to fetch the respective orders.
5. Comparison
These examples show how the use of GraphQL reduces the amount of traffic between the client and server and allows the client to provide some formatting rules for the responses.
It’s worth noting that the data source behind these APIs may still have to execute the same operations to modify or fetch data, but the richness of the interface between client and server allows the client to do less work with GraphQL.
Let’s compare the two approaches further.
5.1. Flexible and Dynamic
GraphQL allows flexible and dynamic queries:
- Client-side applications can request only the required fields
- Aliases can be used to request fields with custom keys
- The client can use the query to manage results order
- The client can be better decoupled from any changes in the API, as there’s no single version of the response object’s structure to follow
5.2. Less Expensive Operations
Every server request has a price of round-trip time and payload size.
In REST, we may end up sending multiple requests to achieve the required functionality. These multiple requests will be an expensive operation. Also, the response payload may have unnecessary data that may not be required by the client-side application.
GraphQL tends to avoid expensive operations. We can often fetch all the data we need in a single request using GraphQL.
5.3. When to Use REST?
GraphQL is not a replacement for REST. Both can even co-exist in the same application. The increased complexity of hosting GraphQL endpoints may be worth it, depending on the use case.
We may prefer REST when:
- Our applications are naturally resource-driven, where the operations are very directly and wholly linked with the individual resource entities
- There’s a need for web caching, as GraphQL does not natively support it
- We require file uploads, as GraphQL doesn’t natively support it
6. Conclusion
In this article, we compared REST and GraphQL using a practical example.
We saw how we might conventionally use each approach learned.
Then, we discussed how neither approach has a clear advantage over the other. Our requirements will be the driving force in making a choice between them. Occasionally, both can co-exist as well.
As always, the example code for this article is available over on GitHub.