DEV Community

Cover image for HATEOAS Principle - Generating Full Paths for Objects in Django Rest Framework.
Stephen Nwankwo
Stephen Nwankwo

Posted on

HATEOAS Principle - Generating Full Paths for Objects in Django Rest Framework.

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:

A sample HATEOAS Json response

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:

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
Enter fullscreen mode Exit fullscreen mode

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
Enter fullscreen mode Exit fullscreen mode

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
Enter fullscreen mode Exit fullscreen mode

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}/"

Enter fullscreen mode Exit fullscreen mode

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__'
Enter fullscreen mode Exit fullscreen mode

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'),
]
Enter fullscreen mode Exit fullscreen mode

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
Enter fullscreen mode Exit fullscreen mode

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',
       ]
   }
Enter fullscreen mode Exit fullscreen mode

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')),
]
   }
Enter fullscreen mode Exit fullscreen mode

Run migrations: Apply the database migrations to create the necessary tables for the models:

   python manage.py makemigrations
   python manage.py migrate
Enter fullscreen mode Exit fullscreen mode

Start the development server: Launch the development server to test the API:

   python manage.py runserver
Enter fullscreen mode Exit fullscreen mode

Here's what the request and response will look like:

Request:

curl --location 'http://127.0.0.1:8000/books/'
Enter fullscreen mode Exit fullscreen mode

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"
    }
]
Enter fullscreen mode Exit fullscreen mode

Do we really need HATEOAS?

Why you may need to implement 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:

  1. 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.

  2. 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.

  3. 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.

  4. 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.

  5. 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

Top comments (0)