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