How to Build a Hypermedia Collection Resource in Python

Creates a paginated hypermedia collection resource with HATEOAS links and embedded items.

Medium Python 3.9+ Aug 9, 2026 API design & gRPC 14 views 0 copies

Python code

61 lines
Python 3.9+
import json
import math


class HypermediaCollection:
    """A mock hypermedia collection resource."""

    def __init__(self, items, base_url="/api/items"):
        self.items = items
        self.base_url = base_url

    def to_dict(self, page=1, per_page=3):
        total = len(self.items)
        pages = math.ceil(total / per_page) if per_page > 0 else 1
        page = max(1, min(page, pages))
        start = (page - 1) * per_page
        end = start + per_page
        page_items = self.items[start:end]

        return {
            "_links": {
                "self": {"href": f"{self.base_url}?page={page}&per_page={per_page}"},
                "first": {"href": f"{self.base_url}?page=1&per_page={per_page}"},
                "last": {"href": f"{self.base_url}?page={pages}&per_page={per_page}"},
                "next": (
                    {"href": f"{self.base_url}?page={page + 1}&per_page={per_page}"}
                    if page < pages else None
                ),
                "prev": (
                    {"href": f"{self.base_url}?page={page - 1}&per_page={per_page}"}
                    if page > 1 else None
                ),
            },
            "page": page,
            "per_page": per_page,
            "total": total,
            "total_pages": pages,
            "_embedded": {
                "items": [
                    {
                        "id": item["id"],
                        "name": item["name"],
                        "_links": {
                            "self": {"href": f"{self.base_url}/{item['id']}"}
                        },
                    }
                    for item in page_items
                ]
            },
        }


if __name__ == "__main__":
    items = [
        {"id": 1, "name": "Alpha"},
        {"id": 2, "name": "Beta"},
        {"id": 3, "name": "Gamma"},
        {"id": 4, "name": "Delta"},
    ]
    collection = HypermediaCollection(items)
    print(json.dumps(collection.to_dict(page=2), indent=2))

Output

stdout
{
  "_links": {
    "self": {
      "href": "/api/items?page=2&per_page=3"
    },
    "first": {
      "href": "/api/items?page=1&per_page=3"
    },
    "last": {
      "href": "/api/items?page=2&per_page=3"
    },
    "next": null,
    "prev": {
      "href": "/api/items?page=1&per_page=3"
    }
  },
  "page": 2,
  "per_page": 3,
  "total": 4,
  "total_pages": 2,
  "_embedded": {
    "items": [
      {
        "id": 4,
        "name": "Delta",
        "_links": {
          "self": {
            "href": "/api/items/4"
          }
        }
      }
    ]
  }
}

How it works

The HypermediaCollection class wraps a list of items and exposes a to_dict method that builds a HAL-style payload. It computes total pages with math.ceil and clamps the requested page into a valid range, so page 5 of 2 pages safely returns page 2. The _links block follows HATEOAS principles by generating navigation URLs for self, first, last, next, and prev. Embedded items include their own self links, making the resource self-describing for API clients. Serializing with json.dumps(indent=2) produces readable HAL JSON for inspection or API responses.

Common mistakes

  • Slicing items without clamping the page, which produces empty embedded arrays for out-of-range pages
  • Hardcoding the base URL instead of passing it as a parameter, breaking portability across environments
  • Forgetting to guard `per_page > 0`, which causes a ZeroDivisionError when calculating total pages

Variations

  1. Return a Python dict and let a web framework (FastAPI or Flask) serialize it with its own JSON renderer
  2. Add query params filtering or sorting before pagination to make the collection more production-ready

Real-world use cases

  • Building a paginated REST API endpoint that returns HATEOAS links for navigation between pages.
  • Mocking a hypermedia-driven backend during frontend development to iterate on client behavior.
  • Generating documentation examples or API contract fixtures that demonstrate pagination and linking.

Sponsored

Run this sample

Open the browser IDE to tweak the example and see results without installing anything.

Open editor

More from API design & gRPC

Related tutorials and quizzes for this topic.