Skip to main content

HATEOAS: REST Level 3 API Design & HAL Implementation

HATEOAS is the highest level of REST maturity — your API responses include links to every action a client can take next. Clients navigate the API like a website, following hyperlinks rather than hardcoding paths. This guide covers the theory and practical HAL implementation.

✓ Richardson Maturity Model explained ✓ HAL+JSON format & examples ✓ Node.js & Spring HATEOAS ✓ IANA link relations reference

Richardson Maturity Model — Levels 0 to 3

Level Name Description Example
0 Swamp of POX Single endpoint, all operations via POST POST /api {action: "getUser"}
1 Resources Multiple resource URLs, still mostly POST POST /users/42
2 HTTP Verbs Correct verbs + status codes per operation GET /users/42 → 200
3 Hypermedia Responses include _links to available actions {"_links": {"self": ...}}

HAL+JSON Response Format

HAL uses _links for hypermedia controls and _embedded for related resources. The Content-Type is application/hal+json:

// GET /orders/42
// Content-Type: application/hal+json

{
  "id": 42,
  "total": 149.99,
  "status": "pending",
  "currency": "USD",
  "_links": {
    "self": { "href": "/orders/42" },
    "cancel": { "href": "/orders/42/cancel", "title": "Cancel this order" },
    "payment": { "href": "/orders/42/payment" },
    "collection": { "href": "/orders" },
    "customer": { "href": "/customers/7" }
  },
  "_embedded": {
    "items": [
      {
        "id": 101,
        "name": "Developer Playbook Pro",
        "quantity": 1,
        "price": 149.99,
        "_links": {
          "product": { "href": "/products/101" }
        }
      }
    ]
  }
}

Node.js HAL Builder

A minimal helper that attaches _links to any response object and sets the correct Content-Type:

// utils/hal.ts
type Link = { href: string; title?: string; templated?: boolean };
type Links = Record<string, Link | Link[]>;

export function hal<T extends object>(
  resource: T,
  links: Links,
  embedded?: Record<string, object | object[]>,
): T & { _links: Links; _embedded?: Record<string, unknown> } {
  return {
    ...resource,
    _links: links,
    ...(embedded ? { _embedded: embedded } : {}),
  };
}

// In a route handler:
app.get('/orders/:id', async (req, res) => {
  const order = await db.orders.findById(req.params.id);
  const canCancel = order.status === 'pending';

  const links: Links = {
    self: { href: `/orders/${order.id}` },
    collection: { href: '/orders' },
    customer: { href: `/customers/${order.customerId}` },
  };

  if (canCancel) {
    links.cancel = { href: `/orders/${order.id}/cancel`, title: 'Cancel order' };
  }

  res.contentType('application/hal+json').json(hal(order, links));
});

HAL vs Siren vs JSON:API Hypermedia

Feature HAL Siren JSON:API
Links _links object links array links object
Actions (forms) Not included actions array Not included
Embedded _embedded entities included
Adoption Widespread Niche Widespread
Content-Type application/hal+json application/vnd.siren+json application/vnd.api+json
Share this tool: X / Twitter LinkedIn
✓ 60,000+ developers use DevPlaybook ★★★★★ 4.8 / 5 on Gumroad ✓ MIT licensed · Instant download ✓ No login required

Related Products

on Gumroad

You Might Also Need

More Free Tools

Enjoying this tool? Get more like it.

Weekly developer tool guides, productivity playbooks & AI tricks. Free. No spam.

DevPlaybook Pro 7 days free

Unlock AI-Powered Dev Tools

  • ⚡ AI Code Review, Doc Generator & SQL Builder
  • ⚡ All premium templates & early access
  • ⚡ Member discounts on Gumroad products
Start Free Trial →

$9/mo after trial · Cancel anytime

Want the full toolkit?

Get DevPlaybook Pro

Every template, guide, boilerplate, and automation script in one bundle. 13 premium products — grab them all at once and save big.

$79
$241 separately Save $162 (67% off)
Get DevPlaybook Pro — $79 →

MIT licensed · Instant download · No subscription

See what's included → Browse all deals →

Frequently Asked Questions

What is HATEOAS and why does it matter?▼

HATEOAS (Hypermedia As The Engine Of Application State) is a constraint of REST that says responses should include links to related actions the client can take next. Instead of hardcoding URLs in the client, the client discovers them from the server's responses. This decouples the client from the URL structure and lets the server evolve its API without breaking clients that follow links.

What is the Richardson Maturity Model?▼

The Richardson Maturity Model (RMM) describes four levels of REST API maturity. Level 0: Single endpoint, RPC-style (SOAP, XML-RPC). Level 1: Multiple resources, each with their own URL. Level 2: HTTP verbs used correctly (GET for reads, POST for creates, DELETE for deletes, appropriate status codes). Level 3: Hypermedia controls — responses include links to available actions. Most APIs reach Level 2; true Level 3 HATEOAS is rare but powerful for complex workflows.

What is HAL (Hypertext Application Language)?▼

HAL is a lightweight convention for embedding hypermedia links in JSON responses. It uses two reserved properties: _links (an object of link relations to URL objects with href) and _embedded (for sideloaded related resources). HAL is simple to implement and widely understood. It is defined in IETF draft RFC draft-kelly-json-hal and has library support in Spring, Node.js, and .NET.

What link relation types should I use?▼

Use IANA-registered link relation types when available: self (canonical URL of this resource), next/prev (pagination), first/last (pagination bounds), related (related resource), item (item within a collection), collection (parent collection). For domain-specific relations, use a URI as the relation type (e.g. https://api.example.com/rels/cancel). Avoid inventing short non-URI relation names as they can clash.

Is HATEOAS practical for modern SPAs and mobile apps?▼

HATEOAS has practical challenges for tightly-typed frontends. SPA and mobile clients often need to know the URL structure at build time for navigation and deep linking. Many teams implement a partial HATEOAS approach: include self links and pagination links (which clients actually use), but stop short of encoding all workflow transitions as links. The key benefit — clients following links rather than hardcoding paths — is most valuable for third-party API consumers.

What is the difference between HAL and Siren?▼

HAL focuses on links and embedded resources — it represents state and navigation. Siren extends this with actions: structured descriptions of forms/mutations (method, fields, type) that clients can use to perform writes without knowing the URL or HTTP method upfront. Siren is more expressive but less widely adopted. HAL is the safer default for most APIs; Siren suits complex workflow-driven APIs.