HATEOAS Principle - Generating Full Paths for Objects in Django Rest Framework.
Stephen Nwankwo
Posted on June 28, 2023
Back-story: Pssft - Please move closer.
So I'm in this particular developers WhatsApp group, where various topics are discussed, and pair-debugging is carried out, amongst other things. Most precious of them all are the infos that are shared - from job openings to latest frameworks and best practices.
On a accursed day, a link was shared. It was a link to a blog post by MIcrosoft, its title was "RESTful web API design". I decided to read through and I came across a concept, which is "HATEOAS"(chill! more info later on). I could have sworn I have seen it somewhere before, but I have never really considered it until now. Fast-forward, it tingled my fancy ;) So I decided to try to implement it using Django Rest Framework(DRF) and I will be showing you how.
--------------- But firstly, here's what I learned about HATEOAS:
What is HATEOAS principle?
HATEOAS (Hypermedia as the Engine of Application State) is a principle in API design that aims to make APIs more discoverable and self-descriptive. In the simplest terms, it means that an API response should include links or references to related resources and actions that can be performed.
Imagine you're browsing a website and you come across a product page. On that page, you not only see information about the product but also links to related actions like "Add to Cart," "View Reviews," or "Similar Products." These links provide you with a way to navigate the website without having to manually construct URLs or know the entire structure of the website beforehand.
HATEOAS applies the same concept to APIs. Instead of just receiving data in an API response, the response also contains links or references to related resources or actions. These links can guide you on how to interact with the API and discover other relevant information. For example, if you receive data about a particular user, the response may include links to update their profile, retrieve their orders, or view their followers.
By including these links in the API responses, clients consuming the API can navigate and interact with the API more easily. They don't need to rely on prior knowledge or hard-coded URLs but can follow the links provided by the API to access different resources or perform actions.
In summary, HATEOAS simplifies API usage by including links or references within API responses, allowing clients to navigate and interact with the API more dynamically and discover its capabilities without extensive prior knowledge.
Enough talk, Here's what a HATEOAS response looks like:
In this example, we have a response for a book resource. The response includes the book's ID, title, and the author's information. The author is represented as a nested object, including their ID and name. Additionally, both the book and the author objects contain a "links" field.
The "links" field within the book object provides references to related resources and actions. In this case:
The "self" link points to the URL for the book resource itself:
"https://api.example.com/books/1".The "reviews" link points to the URL where the client can access the reviews for the book: "https://api.example.com/books/1/reviews".
The "similar_books" link points to the URL where the client can find a list of similar books: "https://api.example.com/books/1/similar".
Similarly, the "links" field within the author object includes a "self" link, which provides the URL for the author's resource: "https://api.example.com/authors/1".
These links allow clients to navigate the API by following the provided URLs, without needing to construct them manually or have prior knowledge of the API's structure. Clients can easily access related resources like reviews or find similar books by simply following the appropriate links in the API response.
Basic Implementation in DRF
The implementation here is rather quick and easy, but might not always be the case depending on project scale.
I would assume you already have knowledge in python, Django and REST, so I won't be talking about all the nitty-gritty of things, only when needed.
Now we know what HATEOAS is mostly about - which is attaching hypermedia or resource links to your API json response. With this article I will be showing you a simple approach to creating such link/path.
Requirements:
-- setup virtual environment(for windows):
$> cd project_folder
$> pip install virtualenv
$> python -m venv venv
$> .\venv\Scripts\activate
Set up a Django project:
Create a new Django project by running the following command in your terminal:
pip install django
pip install djangorestframework
django-admin startproject library_project
Create a new Django app:
Navigate to the project directory and create a new Django app, which will handle the logic for books and authors:
cd library_project
python manage.py startapp library
Define the models: In the library/models.py
file, define the models for books and authors. For example:
from django.db import models
class Author(models.Model):
name = models.CharField(max_length=100)
def __str__(self):
return self.name
def get_absolute_url(self):
return f"/authors/{self.id}/"
class Book(models.Model):
title = models.CharField(max_length=100)
author = models.ForeignKey(Author, on_delete=models.CASCADE)
def __str__(self):
return self.title
def get_absolute_url(self):
return f"/books/{self.id}/"
Create serializers: In the library/serializers.py
file, create serializers for the models defined earlier. Include the SerializerMethodField
to generate the full path for the id
field.
from rest_framework import serializers
from library.models import Author, Book
class AuthorSerializer(serializers.ModelSerializer):
detail = serializers.SerializerMethodField()
def get_detail(self, obj):
request = self.context.get('request')
if request:
return request.build_absolute_uri(obj.get_absolute_url())
return obj.id
class Meta:
model = Author
fields = '__all__'
class BookSerializer(serializers.ModelSerializer):
detail = serializers.SerializerMethodField()
author = AuthorSerializer()
def get_detail(self, obj):
request = self.context.get('request')
if request:
return request.build_absolute_uri(obj.get_absolute_url())
return obj.id
class Meta:
model = Book
fields = '__all__'
Define URLs: In the library/urls.py
file, define the URLs for the API endpoints.
from django.urls import path
from library.views import BookListCreateView, BookRetrieveUpdateDestroyView, AuthorListCreateView
urlpatterns = [
path('books/', BookListCreateView.as_view(), name='book-list-create'),
path('books/<int:pk>/', BookRetrieveUpdateDestroyView.as_view(), name='book-retrieve-update-destroy'),
path('authors/', AuthorListCreateView.as_view(), name='book-list-create'),
]
Create views: In the library/views.py
file, create views for handling the API endpoints.
from rest_framework import generics
from library.models import Book, Author
from library.serializers import BookSerializer, AuthorSerializer
class BookListCreateView(generics.ListCreateAPIView):
queryset = Book.objects.all()
serializer_class = BookSerializer
class BookRetrieveUpdateDestroyView(generics.RetrieveUpdateDestroyAPIView):
queryset = Book.objects.all()
serializer_class = BookSerializer
class AuthorListCreateView(generics.ListCreateAPIView):
queryset = Author.objects.all()
serializer_class = AuthorSerializer
Include the app in the project settings: In the library_project/settings.py
file, include the library
app and configure the REST framework.
INSTALLED_APPS = [
# Other apps...
'library',
'rest_framework',
]
REST_FRAMEWORK = {
'DEFAULT_RENDERER_CLASSES': [
'rest_framework.renderers.JSONRenderer',
]
}
Include a link to the app urls in the project urlr.py file: In the library_project/urls.py
file, include:
from django.contrib import admin
from django.urls import path, include # import include
urlpatterns = [
path('admin/', admin.site.urls),
# include the library app urls module
path('', include('library.urls')),
]
}
Run migrations: Apply the database migrations to create the necessary tables for the models:
python manage.py makemigrations
python manage.py migrate
Start the development server: Launch the development server to test the API:
python manage.py runserver
Here's what the request and response will look like:
Request:
curl --location 'http://127.0.0.1:8000/books/'
Response:
[
{
"id": 1,
"detail": "http://127.0.0.1:8000/books/1/",
"author": {
"id": 1,
"detail": "http://127.0.0.1:8000/authors/1/",
"name": "Stephen Nwankwo"
},
"title": "Tales of Ogun"
},
{
"id": 2,
"detail": "http://127.0.0.1:8000/books/2/",
"author": {
"id": 1,
"detail": "http://127.0.0.1:8000/authors/1/",
"name": "Stephen Nwankwo"
},
"title": "Tales of Ogun"
}
]
Do we really need HATEOAS?
I have browsed through a number of APIs, and didn't actually come across HATEOS implementation. So on the issue of whether one needs it or not is really up to you the developer or project manager.
To help you make that decision, depending on the specific requirements and goals of your project. Here are a few points to consider:
Enhanced discoverability: HATEOAS provides a self-descriptive nature to APIs, allowing clients to discover and navigate the available resources dynamically. It eliminates the need for clients to rely on prior knowledge or hard-coded URLs, making the API more intuitive and easier to use.
Reduced coupling: By including links or references to related resources in API responses, HATEOAS reduces the coupling between clients and servers. Clients can follow the provided links to access different resources and perform actions without needing to know the entire API structure in advance. This promotes loose coupling and enables more flexible and extensible systems.
Improved scalability: HATEOAS enables the server to evolve and add new resources or actions without breaking existing clients. Clients that follow links provided in API responses can adapt to changes in the API without requiring modifications to their code. This makes the API more scalable and facilitates versioning and backward compatibility.
Client development simplicity: HATEOAS simplifies client development by providing a guided approach to API interaction. Clients can rely on the links provided in the API responses instead of constructing URLs manually or hard-coding them. This reduces the complexity and potential errors in client code.
Consistency and documentation: HATEOAS promotes consistency and provides a form of self-documentation within the API itself. The links included in the responses serve as a reference to the available actions and resources, making it easier for developers to understand and utilize the API effectively.
However, it's important to note that implementing HATEOAS adds complexity to the API design and development process. It requires careful consideration of the links to include, balancing between providing enough information and avoiding overwhelming clients with excessive links. Additionally, HATEOAS may not be necessary or appropriate for all API use cases, especially when the API's scope is small or the client-server interaction is straightforward.
Ultimately, the decision to use HATEOAS should be based on factors such as the complexity of your API, the desired level of discoverability and flexibility, and the needs of your clients.
Please leave a like if you found the article useful, also feel free to share your opinions in the comment section.
References:
https://learn.microsoft.com/en-us/azure/architecture/best-practices/api-design
https://www.geeksforgeeks.org/hateoas-and-why-its-needed-in-restful-api/
https://www.w3schools.in/restful-web-services/rest-apis-hateoas-concept#google_vignette
Posted on June 28, 2023
Join Our Newsletter. No Spam, Only the good stuff.
Sign up to receive the latest update from our blog.
Related
June 28, 2023