Introduction to HATEOAS in REST API Design
In this tutorial, you will learn about Introduction to HATEOAS in REST API ilink "Api Design" >}}. We cover key concepts, practical examples, and best practices to help you master this topic.
HATEOAS (Hypermedia as the Engine of Application State) is a REST constraint where API responses include hypermedia links that guide clients to discover available actions and related resources dynamically.
flowchart LR
A[Client] -->|GET /users/42| B[Server]
B --> C[Response with Links]
C --> D[{"id":42, "links": {"self": "/users/42", "orders": "/users/42/orders"}}]
D --> E[Client follows links]
style A fill:#e1f5fe
style B fill:#f3e5f5
style D fill:#c8e6c9
HATEOAS responses include link objects with rel (relationship) and href (target URI). A user response might include links to their orders, profile, and the ability to update or delete. Clients navigate the API by following links rather than constructing URLs.
Think of HATEOAS like a website vs a printed menu. A website has clickable links (HATEOAS) that guide you to related pages. A printed menu (non-HATEOAS) lists dishes without connections. With HATEOAS, the API tells you where to go next. Without it, you must know the URLs ahead of time.
Example: Response Without HATEOAS
import requests
# Client must know the URI structure
response = requests.get("https://api.example.com/users/42")
user = response.json()
print(user)
# Client hard-codes URI patterns
orders = requests.get(f"https://api.example.com/users/{user['id']}/orders")
Expected output:
{"id": 42, "name": "Alice", "email": "alice@example.com"}
Example: Response With HATEOAS
import requests
response = requests.get("https://api.example.com/users/42")
user = response.json()
print(json.dumps(user, indent=2))
# Client discovers links dynamically
orders_link = user["_links"]["orders"]["href"]
orders = requests.get(f"https://api.example.com{orders_link}")
print(f"Orders: {orders.json()}")
Expected output:
{
"id": 42,
"name": "Alice",
"email": "alice@example.com",
"_links": {
"self": {"href": "/users/42", "rel": "self"},
"orders": {"href": "/users/42/orders", "rel": "collection"},
"update": {"href": "/users/42", "rel": "edit"},
"delete": {"href": "/users/42", "rel": "delete"}
}
}
Orders: [{"id": 5, "total": 100.00, "_links": {...}}]
Example: HATEOAS Actions
import requests
# Response includes available actions based on state
response = requests.get("https://api.example.com/orders/5")
order = response.json()
for link in order["_links"]["actions"]:
print(f"{link['name']:10s} -> {link['method']:6s} {link['href']}")
# Client follows the cancel action
if any(a["name"] == "cancel" for a in order["_links"]["actions"]):
cancel_link = [a for a in order["_links"]["actions"] if a["name"] == "cancel"][0]
cancel_response = requests.post(
f"https://api.example.com{cancel_link['href']}"
)
print(f"Cancel status: {cancel_response.status_code}")
Expected output:
self -> GET /orders/5
update -> PUT /orders/5
cancel -> POST /orders/5/cancel
Cancel status: 200
Common Mistakes
- Not including links in responses — Without HATEOAS, clients must hard-code URL patterns, which breaks when the API structure changes.
- Including every possible link — Only include links relevant to the current resource state. A paid order should not show a cancel link.
- Using absolute URLs inconsistently — Use either absolute or relative URLs consistently. Relative URLs are more portable across environments.
- Not documenting link relations — The rel (relationship) value should follow a standard naming convention so clients know what each link does.
- Requiring clients to construct URLs — If clients must combine a base URL with a path, you are doing HATEOAS wrong. Send complete URLs.
Practice Questions
- What does HATEOAS stand for?
- How do clients discover available actions in a HATEOAS API?
- What is the purpose of the rel attribute in a hypermedia link?
- How does HATEOAS improve API evolvability?
- Challenge: Design a HATEOAS response for a blog post resource that includes links to the author, comments, related posts, and actions (edit, delete, publish) based on the post state.
FAQ
Mini Project
Build a Python client that consumes a HATEOAS API. The client should never construct URLs manually. Instead, it follows links from each response to discover resources and actions. Implement a recursive link-following navigation system.
What's Next
Now learn about API Versioning in REST API Design.
Built by the developers of DodaTech
Doda Browser, DodaZIP & Durga Antivirus Pro