Skip to content

HATEOAS Mini Project — Build a Hypermedia-Driven Order API

DodaTech Updated 2026-06-28 2 min read

In this tutorial, you will learn about HATEOAS Mini Project. We cover key concepts, practical examples, and best practices to help you master this topic.

Build a complete HATEOAS-compliant order management API with state machine transitions, link generation, and a generic hypermedia client that navigates entirely through links.

What You'll Learn

  • Implementing HATEOAS with Express and Python
  • Building a state machine for resource transitions
  • Creating a generic hypermedia client

Why It Matters

This project demonstrates the full HATEOAS workflow: server generates links based on resource state, and client follows links without hardcoded URLs.

Server (Python Flask)

from flask import Flask, jsonify
from enum import Enum

class OrderStatus(Enum):
    PENDING = "pending"
    PAID = "paid"
    SHIPPED = "shipped"
    DELIVERED = "delivered"

transitions = {
    OrderStatus.PENDING: {
        "pay": {"method": "POST", "url": "/orders/{id}/payments"},
        "cancel": {"method": "DELETE", "url": "/orders/{id}"}
    },
    OrderStatus.PAID: {
        "ship": {"method": "POST", "url": "/orders/{id}/shipments"},
        "refund": {"method": "POST", "url": "/orders/{id}/refunds"}
    },
    OrderStatus.SHIPPED: {
        "track": {"method": "GET", "url": "/orders/{id}/tracking"}
    }
}

def generate_links(order):
    actions = transitions.get(order["status"], {})
    links = {
        "self": {"href": f"/orders/{order['id']}", "method": "GET"},
    }
    for rel, action in actions.items():
        links[rel] = {
            "href": action["url"].format(id=order["id"]),
            "method": action["method"]
        }
    return links

Client (Generic HATEOAS)

class HypermediaClient:
    def __init__(self, base_url):
        self.base_url = base_url
        import requests
        self.http = requests.Session()

    def get(self, url):
        return self.http.get(f"{self.base_url}{url}").json()

    def follow(self, resource, rel):
        link = resource["_links"][rel]
        return self.get(link["href"])

    def act(self, resource, rel, data=None):
        action = resource["_links"][rel]
        method = action.get("method", "POST").lower()
        url = f"{self.base_url}{action['href']}"
        return getattr(self.http, method)(url, json=data).json()

Testing

client = HypermediaClient("http://localhost:5000")
root = client.get("/api")
orders = client.follow(root, "orders")
order = client.follow(orders, "first")
payment = client.act(order, "pay", {"amount": 2999})
print("Payment result:", payment)

Common Mistakes

After payment, the order should show shipping links instead of payment links.

Ensure your link graph has no cycles that could cause infinite client loops.

Add links for error states like payment failure or shipment cancellation.

Clients need to know whether to GET, POST, PUT, or DELETE.

5. Not Testing the Full Client Flow

Test that a client can navigate from entry point through complete workflows.

FAQ

Should the project use a database?

: Optional. Start with in-memory data, then add a database.

How do I test the generic client?

: Write tests that verify the client follows links correctly through workflows.

Can I deploy this to production?

: With a real database, error handling, and security, yes.

What's Next

Your HATEOAS knowledge is complete. Explore API Documentation with OpenAPI, or API Pagination for common patterns.

Built by the developers of DodaTech

Doda Browser, DodaZIP & Durga Antivirus Pro