HATEOAS: что такое

HATEOAS (Hypermedia As The Engine Of Application State) — принцип REST, при котором каждый ответ API содержит ссылки на связанные ресурсы и доступные действия.

Зачем нужно

Без HATEOAS клиент «знает» структуру URL заранее и зависит от неё. С HATEOAS сервер сам сообщает, что можно сделать следующим: перейти к постам, обновить пользователя, удалить. Это позволяет менять URL-структуру на сервере, не ломая клиентов, и делает API самодокументируемым.

Где используется

  • Enterprise REST API (Spring HATEOAS, Symfony)
  • Платёжные API (PayPal REST API — классический пример HATEOAS)
  • Hypermedia-клиенты, навигирующие API автоматически
  • Государственные и банковские API с жёсткими требованиями к REST

Пример ответа с HATEOAS

// GET /api/orders/123 → 200 OK
{
  "id": 123,
  "status": "pending",
  "amount": 2500,
  "currency": "RUB",
  "_links": {
    "self":    { "href": "/api/orders/123", "method": "GET" },
    "cancel":  { "href": "/api/orders/123/cancel", "method": "POST" },
    "pay":     { "href": "/api/orders/123/pay", "method": "POST" },
    "customer":{ "href": "/api/customers/42", "method": "GET" }
  }
}
// После оплаты — доступные действия меняются
// GET /api/orders/123 → 200 OK
{
  "id": 123,
  "status": "paid",
  "amount": 2500,
  "_links": {
    "self":    { "href": "/api/orders/123" },
    "refund":  { "href": "/api/orders/123/refund", "method": "POST" },
    "receipt": { "href": "/api/orders/123/receipt", "method": "GET" }
  }
}

Реализация в Express

app.get('/api/orders/:id', (req, res) => {
  const order = db.orders.find(req.params.id);
  const base = `/api/orders/${order.id}`;

  const links = { self: { href: base } };

  if (order.status === 'pending') {
    links.cancel() = { href: `${base}/cancel`, method: 'POST' };
    links.pay    = { href: `${base}/pay`,    method: 'POST' };
  }

  if (order.status === 'paid') {
    links.refund  = { href: `${base}/refund`,  method: 'POST' };
    links.receipt = { href: `${base}/receipt`, method: 'GET'  };
  }

  res.json({ ...order, _links: links });
});

Клиент, использующий ссылки

// Клиент не знает URL заранее, идёт по ссылкам
const order = await api.get('/api/orders/123');

if (order._links.pay) {
  await api.request(order._links.pay.href, {
    method: order._links.pay.method,
  });
}

Частые ошибки

  • Жёсткое кодирование URL на клиенте — теряется смысл HATEOAS
  • Добавление _links без изменения набора ссылок в зависимости от состояния — декоративный HATEOAS
  • Игнорирование стандартов (HAL, JSON:API, Siren) — изобретение своего формата ссылок
  • Путаница с REST Level 3 (Richardson Maturity Model) — HATEOAS это именно уровень 3

Связанные темы

Ресурсы