Skip to content

HATEOAS Clients — Building Applications That Navigate Hypermedia APIs

DodaTech Updated 2026-06-28 2 min read

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

HATEOAS clients consume hypermedia APIs by following links discovered at runtime, starting from a root entry point and navigating through resource relationships without hardcoded URL patterns.

What You'll Learn

  • How to build clients that follow hypermedia links
  • Error handling and fallback patterns for HATEOAS
  • Testing hypermedia client behavior

Why It Matters

A true HATEOAS client works with any API that follows the same hypermedia conventions. This is the vision of REST: clients that adapt to server changes automatically.

Code Examples

# HATEOAS client for a banking API
class BankingClient:
    def __init__(self, entry_point):
        self.api = HypermediaClient(entry_point)

    def list_accounts(self):
        root = self.api.get()
        accounts = self.api.follow(root, "accounts")
        return accounts

    def transfer(self, from_account, to_account, amount):
        # Discover the transfer action from the account
        from_acct = self.api.get(f"/accounts/{from_account}")
        transfer_link = from_acct["_actions"]["transfer"]
        result = self.api.post(transfer_link["href"], {
            "to": to_account,
            "amount": amount
        })
        return result

    def get_statement(self, account_id):
        acct = self.api.get(f"/accounts/{account_id}")
        statement = self.api.follow(acct, "statement")
        return statement
// Generic hypermedia client
class HATEOASClient {
  async get(url) {
    const res = await fetch(url);
    return res.json();
  }

  async follow(resource, rel) {
    const link = resource?._links?.[rel];
    if (!link) throw new Error(`No '${rel}' link`);
    return this.get(link.href);
  }

  async act(resource, rel, data) {
    const action = resource?._actions?.[rel];
    if (!action) throw new Error(`No '${rel}' action`);
    return fetch(action.href, {
      method: action.method,
      body: JSON.stringify(data)
    });
  }
}

Common Mistakes

1. Falling Back to URL Construction

When a link is missing, clients should report it, not construct URLs manually.

A client should check if a link exists before trying to follow it.

3. Ignoring Action Method Requirements

Following a POST action with a GET request fails. Check the method.

4. Hardcoding Resource Types

Don't assume a resource structure based on its URL path. Use link rels.

5. Not Re-fetching Resources After State Changes

After performing an action, re-fetch the resource to get updated links.

Practice Questions

  1. What is the first request a HATEOAS client makes?
  2. How does a client find related resources?
  3. What should a client do if a link is missing?
  4. Why should clients re-fetch resources after state changes?
  5. How does a HATEOAS client differ from a regular API client?

Answers:

  1. A GET to the root entry point to discover available resources.
  2. By reading _links from resource responses and following them.
  3. Handle gracefully and report that the action is unavailable.
  4. State changes update available links and actions.
  5. It uses links from responses instead of hardcoded URL templates.

Challenge: Build a HATEOAS client for the GitHub API. The client should start at https://api.github.com and navigate to a user's repositories, then to a specific repo's issues.

FAQ

Are HATEOAS clients more complex to build?

: Yes, initially. But they're more resilient to API changes.

Can I use fetch() directly with HATEOAS?

: Yes. You just need to parse _links from JSON responses.

Do HATEOAS clients work with non-HATEOAS APIs?

: No. They depend on hypermedia links in responses.

What's Next

Compare HATEOAS vs GraphQL to understand when each approach shines, then explore HATEOAS Design Patterns.

Built by the developers of DodaTech

Doda Browser, DodaZIP & Durga Antivirus Pro