Every once in a while, almost every software developer in the world has to investigate what technology to use when faced with a particular task at hand. Usually these technologies come in pairs. This time my concern — and the concern of the team I've been working with — was to suggest to our customer which technology to use for their project: REST or GraphQL.
REST (Representational State Transfer) is an API that conforms to a set of constraints. It is an architectural style — one needs to change a URL in order to retrieve different information. It supports GET, POST, PUT, PATCH, and DELETE HTTP requests; and JSON, XML, and YAML data formats.
There might be some concerns with over- or under-fetching since the output is always static. Naturally, there are some solutions to mitigate that. One can implement query parameters for the endpoint and this way control either filtering or a set of fields returned to a client.
In a way, GraphQL (Graph Query Language) is a response to the problems in REST. There is only one endpoint, and to get different information, one only needs to change the query. It only supports POST HTTP requests. For simple projects it adds “unnecessary” complexity that some teams might not like.
Usually, GraphQL is very good at resolving over- or under-fetching issues since it will only provide the fields requested in the query. Above all a lot of developers seem to like it in it is gaining more and more traction.
Representational state transfer (REST) is a dominant style for modern API architecture. According to Postman’s 2022 State of the API report, 89% of developers use REST. GraphQL is in fourth place at 38%, superseded by webhooks (35%) and SOAP (34%).
There is a wide variety of companies using GraphQL. This doesn’t necessarily mean they use it exclusively or instead of REST API. But they are definitely seeing great value in it, e.g.:
I will try to create a very simple example for each approach. Since I was doing research for a Django project, I will also implement it here. I will use a requirements.txt file with the following contents:
Django==4.1.7 djangorestframework==3.14.0 graphene-django==3.0.0
After that, we create our project:
mkdir rest-graphql-django cd rest-graphql-django python3 -m venv .venv . .venv/bin/activate pip install -U pip pip install -r requirements.txt django-admin startproject app . ./manage.py startapp api
I am going to use the api
app to set up my example project.
In app/settings.py
let’s add:
INSTALLED_APPS = [ ... "django.contrib.staticfiles", "api.apps.ApiConfig", "graphene_django", "rest_framework", ]
Contents of the api/models.py
:
from django.db import models # Create your models here. class Author(models.Model): first_name = models.TextField(max_length=255) last_name = models.TextField(max_length=255) def __str__(self): return f"{self.first_name} {self.last_name}" class Book(models.Model): title = models.TextField(max_length=255) year = models.IntegerField(default=0) author = models.ForeignKey(Author, on_delete=models.CASCADE, null=True, blank=True) def __str__(self): return f"{self.title}"
Contents of the api/serializers.py
:
from rest_framework import serializers from api.models import Author from api.models import Book class AuthorSerializer(serializers.HyperlinkedModelSerializer): class Meta: model = Author fields = "__all__" class BookSerializer(serializers.HyperlinkedModelSerializer): class Meta: model = Book fields = "__all__"
Contents of the api/views.py
:
from api.models import Author from api.models import Book from api.serializers import AuthorSerializer from api.serializers import BookSerializer from rest_framework import viewsets # Create your views here. class AuthorViewSet(viewsets.ModelViewSet): queryset = Author.objects.all() serializer_class = AuthorSerializer class BookViewSet(viewsets.ModelViewSet): queryset = Book.objects.all() serializer_class = BookSerializer
Contents of the api/schema.py
:
import graphene from graphene_django import DjangoObjectType from api.models import Author from api.models import Book class AuthorType(DjangoObjectType): class Meta: model = Author fields = ("id", "first_name", "last_name") class BookType(DjangoObjectType): class Meta: model = Book fields = ("id", "title", "year", "author") class Query(graphene.ObjectType): hello = graphene.String(default_value="Hi!") authors = graphene.List(AuthorType) books = graphene.List(BookType) def resolve_authors(root, info): return Author.objects.all() def resolve_books(root, info): return Book.objects.all() schema = graphene.Schema(query=Query)
In app/settings.py
I need to also add:
GRAPHENE = { "SCHEMA": "api.schema.schema" }
Finally, we need to update the app/urls.py
file:
from django.contrib import admin from django.urls import include from django.urls import path from graphene_django.views import GraphQLView from rest_framework import routers from api.views import AuthorViewSet from api.views import BookViewSet router = routers.DefaultRouter() router.register(r"authors", AuthorViewSet) router.register(r"books", BookViewSet) urlpatterns = [ path("", include(router.urls)), path("graphql", GraphQLView.as_view(graphiql=True)), path("admin/", admin.site.urls), ]
When running the app locally and navigating to http://127.0.0.1:8000/ we are able to see both endpoints: for authors and books. We can also navigate to each particular endpoint.
On the other hand, navigating to http://127.0.0.1:8000/graphql we will find our GraphiQL web interface to write the queries and get results.
In this simple example, we can see that we can fetch the data either way. Advantages and disadvantages of each approach have to be pondered on a case-by-case basis.
REST | GraphQL |
An architectural style largely viewed as a conventional standard for designing APIs |
A query language for solving common problems when integrating APIs |
Deployed over a set of URLs where each of them exposes a single resource |
Deployed over HTTP using a single endpoint that provides the full capabilities of the exposed service |
Uses a server-driven architecture |
Uses a client-driven architecture |
Uses caching automatically |
Lacks built-in caching mechanism |
Supports multiple API versions |
No API versioning required |
Response output usually in XML, JSON, and YAML |
Response output in JSON |
Doesn't offer type-safety or auto-generated documentation, requires 3rd party software |
Offers type-safety and auto-generated documentation |
Simplifying work with multiple endpoints requires expensive custom middleware |
Allows for schema stitching and remote data fetching |
GraphQL enables clients to run queries and get exactly what they ask for. But if GraphQL API is not carefully designed, it can overwhelm the server. Too many fields and too many nested queries could eventually bring it down. In this case, the REST approach might be more suitable since you can set up multiple endpoints for various scenarios and fine tune the queries.
I guess it is also important to have in mind the development team when deciding which tool to use. If the team is not comfortable with a particular tool, it might be better not to use it in the first place.
I believe GraphQL is an alternative to REST, not a replacement or a successor. There is also no one-fits-all approach on whether to choose one against the other. A careful examination of pros and cons in your particular case will let you know what would fit best. There are a number of articles online that favor either REST or GraphQL. Sometimes the arguments are very good, but sometimes they’re not really convincing. In the end, it’s up to you to do your homework.