17 - HATEOAS : des liens dans tes réponses
Ce que tu vas apprendre
- Ce que signifie HATEOAS et pourquoi ca existe
- Le format HAL pour structurer les liens
- Comment l'API GitHub utilise l'hypermedia
- Pourquoi la plupart des APIs internes n'en ont pas besoin
Prerequisites
16 - Relations entre ressources
Tu connais ces sites ou chaque page contient des liens vers les pages suivantes. Tu cliques, tu decouvres. Tu n'as pas besoin de deviner l'URL. Le web fonctionne comme ca depuis 1991, et HATEOAS essaie de faire pareil pour les APIs.
Le dernier niveau de Richardson
Le modèle de maturite de Richardson definit 4 niveaux pour les APIs REST. Le niveau 3, le plus eleve, c'est HATEOAS : Hypermedia As The Engine Of Application State.
L'idee : chaque réponse de l'API contient des liens vers les actions possibles. Le client n'a pas besoin de construire les URLs lui-meme. Il suit les liens.
json{
"id": "order-42",
"status": "pending",
"total": 8500,
"_links": {
"self": { "href": "/orders/order-42" },
"cancel": { "href": "/orders/order-42/cancel", "method": "POST" },
"payment": { "href": "/orders/order-42/pay", "method": "POST" },
"customer": { "href": "/users/user-7" }
}
}
Si la commande est deja payee, le lien payment disparaît. Le client sait ce qui est possible en lisant les liens, pas en codant en dur la logique métier.
Le format HAL
HAL (Hypertext Application Language) est le format le plus repandu pour HATEOAS. Il utilise deux propriétés reservees :
_links: les liens vers d'autres ressources_embedded: des ressources imbriquees directement dans la réponse
json{
"_links": {
"self": { "href": "/orders?page=2" },
"next": { "href": "/orders?page=3" },
"prev": { "href": "/orders?page=1" }
},
"_embedded": {
"orders": [
{
"id": "order-42",
"total": 8500,
"_links": {
"self": { "href": "/orders/order-42" }
}
},
{
"id": "order-43",
"total": 3200,
"_links": {
"self": { "href": "/orders/order-43" }
}
}
]
},
"totalItems": 156,
"page": 2
}
Le Content-Type pour HAL est application/hal+json. D'autres formats existent : JSON:API, Siren, Hydra. Mais HAL reste le plus simple a implementer et a comprendre.
L'API GitHub comme exemple
GitHub est l'une des rares APIs publiques qui utilise HATEOAS serieusement. Chaque réponse contient des URLs completes :
json{
"id": 1,
"name": "mon-repo",
"full_name": "nicolas/mon-repo",
"html_url": "https://github.com/nicolas/mon-repo",
"url": "https://api.github.com/repos/nicolas/mon-repo",
"issues_url": "https://api.github.com/repos/nicolas/mon-repo/issues{/number}",
"pulls_url": "https://api.github.com/repos/nicolas/mon-repo/pulls{/number}"
}
Le client n'a pas besoin de savoir que l'URL des issues est /repos/{owner}/{repo}/issues. Il la trouve dans la réponse. Si GitHub change la structure des URLs demain, les clients qui suivent les liens ne cassent pas.
Les URL templates ({/number}) suivent la RFC 6570. Le client remplace les variables par les valeurs reelles.
Quand HATEOAS est excessif
Je vais etre honnete : dans la majorite des projets sur lesquels j'ai travaille, HATEOAS n'apportait rien. Voici pourquoi.
APIs internes : quand le frontend et le backend sont développés par la meme équipe, tout le monde connaît les URLs. Ajouter des _links partout, c'est du bruit dans les réponses pour un gain nul.
SPAs avec routes hardcodees : ton application React a des routes figees. Elle ne va pas "découvrir" dynamiquement ou aller en suivant des liens API. Le frontend construit ses URLs avec un SDK ou des helpers.
Surcout non negligeable : générer les liens pour chaque réponse prend du temps cote serveur. Calculer les actions possibles selon l'état de la ressource et les permissions de l'utilisateur, ca veut dire exécuter de la logique métier dans le serializer.
Quand ca vaut le coup :
- APIs publiques consommees par des tiers (ils ne connaissent pas ton code)
- APIs avec des workflows complexes ou les actions possibles changent selon l'état
- Quand tu veux pouvoir changer tes URLs sans casser les clients
Sur paltemps.fr, l'API est consommee uniquement par le frontend. Pas de HATEOAS. Les URLs sont dans un fichier de constantes partage. Simple et suffisant.
Implementer HATEOAS progressivement
Si tu decides que HATEOAS a du sens pour ton cas, commence petit :
typescriptfunction serializeOrder(order: Order, user: User) {
const links: Record<string, { href: string }> = {
self: { href: `/orders/${order.id}` }
};
if (order.status === "pending") {
links.cancel = { href: `/orders/${order.id}/cancel` };
links.pay = { href: `/orders/${order.id}/pay` };
}
if (order.status === "shipped") {
links.track = { href: `/orders/${order.id}/tracking` };
}
if (user.role === "admin") {
links.refund = { href: `/orders/${order.id}/refund` };
}
return { ...order, _links: links };
}
La logique conditionnelle des liens reflete exactement les transitions possibles de ta machine d'états. Si tu as deja une state machine propre, les liens HATEOAS en decoulent naturellement.
Self-describing APIs
HATEOAS fait partie d'un concept plus large : les APIs auto-descriptives. L'idee est qu'un client puisse explorer l'API sans documentation, juste en suivant les liens depuis le point d'entree.
bash# Point d'entree
curl https://api.example.com/
{
"_links": {
"users": { "href": "/users" },
"orders": { "href": "/orders" },
"products": { "href": "/products" },
"docs": { "href": "/docs" }
}
}
C'est elegant en theorie. En pratique, les développeurs lisent la documentation et utilisent des SDK. Personne ne "decouvre" une API en suivant des liens a la main. Mais pour de l'outillage automatise (crawlers, generateurs de SDK), ca peut avoir de la valeur.
Résumé
- HATEOAS ajoute des liens dans les réponses pour guider le client vers les actions possibles
- Le format HAL (
_links,_embedded) est le plus courant - L'API GitHub est un bon exemple d'utilisation réelle
- Pour les APIs internes et les SPAs, c'est souvent du bruit inutile
- Si tu l'implementes, commence par les liens conditionnels lies aux transitions d'état
Article précédent : 16 - Relations entre ressources Article suivant : 18 - OpenAPI : spécifier ton API