WiFi Direct

API REST : conception et bonnes pratiques pour des interfaces solides

Une API REST est un contrat, pas juste des URL qui renvoient du JSON. Découvrez les principes qui tiennent en production, les pièges vécus et pourquoi certaines conventions sont mal comprises.

API REST : conception et bonnes pratiques pour des interfaces solides

Un client m'a appelé un jour parce que son application mobile affichait des montants en euros là où il attendait des dollars. Le développeur backend avait renommé le champ amount en amount_eur, en se disant que ce serait « plus clair ». Sauf que l'app mobile lisait encore amount. Résultat : trois jours de debug, un correctif en urgence, et une leçon que je n'ai pas oubliée sur ce qu'est vraiment une API REST bien conçue.

Une API REST, ce n'est pas juste des URL qui renvoient du JSON. C'est un contrat. Et un contrat, ça se casse vite quand on ne respecte pas quelques règles de base. Je vous propose de voir ensemble les principes qui tiennent dans le temps, les pièges que j'ai rencontrés en production, et pourquoi certaines conventions que tout le monde répète sont en réalité mal comprises.

Points clés à retenir

  • Une API REST se pense comme un contrat stable : les ressources sont des noms, les verbes HTTP portent l'action.
  • Le versioning dans l'URL (/v1/) reste le plus lisible, mais il ne dispense pas d'une vraie politique de dépréciation.
  • Les codes de statut HTTP ne sont pas décoratifs : un 200 renvoyé sur une erreur métier casse le client.
  • Idempotence, ETags et clés d'idempotence sont la différence entre une API qui survit aux retries et une qui crée des doublons.
  • Les erreurs méritent un format normalisé (RFC 7807) plutôt qu'un message libre.
  • Une spécification OpenAPI est le seul document qui reste vrai quand le code change.

API REST : ce que la définition cache vraiment

REST, c'est l'acronyme de Representational State Transfer. Derrière le terme, cinq contraintes posées par Roy Fielding dans sa thèse. La plupart des articles que je lis en citent trois et oublient les deux plus intéressantes.

Les contraintes qui comptent en pratique

L'interface uniforme, d'abord : les ressources sont identifiées par des URI, et la manipulation passe par des représentations (souvent du JSON). Le client et le serveur sont découplés, chacun évolue de son côté tant que le contrat est respecté.

Le sans-état, ensuite. Chaque requête porte tout ce qu'il faut pour être comprise. Pas de session côté serveur.

Et là, la contrainte que tout le monde balance dans ses slides sans la comprendre : HATEOAS. L'idée, c'est qu'une réponse contient les liens vers les actions possibles. Un client qui découvre l'API dynamiquement n'a pas besoin de connaître les URL à l'avance.

HATEOAS, utile ou théorique ?

Franchement, dans la plupart des projets que j'ai vus, HATEOAS n'est pas implémenté. Et ce n'est pas forcément un drame. Quand vous avez un client unique (votre propre front), construire un moteur de navigation hypermédia est un coût que personne ne rentabilise.

Par contre, dès qu'une API est publique et consommée par des tiers, je change d'avis. J'ai vu une API de paiement qui exposait les liens next, refund et cancel directement dans les réponses. Le gain : quand l'équipe a ajouté une action dispute, aucun client n'a eu à modifier son code pour la découvrir. C'est rare, mais ça justifie l'effort.

Convention de nommage : les règles que j'applique sans exception

Une URL bien nommée se lit comme une phrase. Le piège classique, c'est de mettre un verbe dans le chemin. /getUsers, /createOrder, /deleteProduct/42. À chaque fois que je vois ça dans une revue de code, je sais que l'API a été pensée comme du RPC déguisé.

Ressources au pluriel, en minuscules

La ressource est un nom, au pluriel, en minuscules, avec des tirets si besoin. /users, /order-items, /invoices. L'identifiant suit : /users/42. Pour une relation, on imbrique avec parcimonie : /users/42/orders. Deux niveaux maximum, au-delà ça devient illisible.

Le verbe, lui, c'est la méthode HTTP qui le porte :

  • GET : lire, jamais modifier, idempotent.
  • POST : créer une sous-ressource ou déclencher une action non idempotente.
  • PUT : remplacer intégralement une ressource. Idempotent.
  • PATCH : modification partielle. Pas forcément idempotent selon l'implémentation.
  • DELETE : supprimer. Idempotent, oui, même s'il renvoie 404 au deuxième appel.

PUT contre PATCH : l'erreur que je vois le plus

La nuance qui échappe à beaucoup : PUT remplace. Si vous envoyez PUT /users/42 avec seulement {"email": "..."}, vous dites au serveur que tous les autres champs doivent disparaître. Beaucoup d'implémentations font un merge silencieux, ce qui viole la sémantique et crée des bugs impossibles à reproduire.

Pour une modification partielle, utilisez PATCH. Et si vous voulez être rigoureux, le format JSON Merge Patch (RFC 7396) est un bon standard à adopter.

Codes de statut et gestion des erreurs

Un 200 OK qui contient {"error": "utilisateur introuvable"}, c'est le genre de chose qui m'a coûté une demi-journée de debug la première fois que je l'ai croisée. Le client HTTP voit un succès, le code continue, et l'erreur explose trois couches plus haut.

Codes de statut et gestion des erreurs

Les codes existent pour une raison : dire au client ce qui s'est passé au niveau du protocole, avant même de lire le corps.

Situation Code à renvoyer Corps attendu
Création réussie 201 Created Ressource créée + en-tête Location
Lecture réussie sans contenu 204 No Content Vide
Données invalides 400 Bad Request Détail des champs en erreur
Non authentifié 401 Unauthorized Indication du schéma d'auth
Authentifié mais interdit 403 Forbidden Aucune info sensible
Ressource absente 404 Not Found Message neutre
Conflit de version 409 Conflict État actuel de la ressource

RFC 7807 : un format d'erreur normalisé

Plutôt que d'inventer un format maison à chaque projet, adoptez Problem Details. Le principe : une structure JSON standard avec type, title, status, detail et instance. Le client sait où lire l'information sans deviner.

Ce que ça change concrètement : une bibliothèque cliente générique peut gérer les erreurs sans code spécifique. Sur un projet e-commerce il y a deux ans, on est passés d'un format d'erreur qui variait selon les endpoints à du RFC 7807 strict. Le temps moyen de résolution d'un incident côté support a chuté de moitié.

Versioning et dépréciation : le sujet qu'on repousse

Versionner dans l'URL (/v1/users) reste ma recommandation par défaut. C'est visible, loggable, et facile à router. Versionner via l'en-tête Accept est plus élégant sur le papier, mais en pratique ça complique le debug et les caches.

Versioning et dépréciation : le sujet qu'on repousse

Le vrai sujet, ce n'est pas le format. C'est ce qu'on fait quand une version doit mourir.

Une politique de dépréciation qui tient

Trois en-têtes à utiliser systématiquement :

  1. Deprecation avec la date à laquelle la version devient obsolète.
  2. Sunset avec la date d'arrêt effectif.
  3. Link pointant vers la documentation de migration.

J'ai vu une équipe couper une version sans préavis parce qu'« aucun client important ne l'utilisait ». Sauf qu'un client important l'utilisait, et qu'on l'a appris par un ticket de production un vendredi soir. Depuis, je préconise un minimum de six mois entre l'annonce et l'arrêt, avec des relances à J-90, J-30 et J-7 par email aux propriétaires d'applications identifiés.

Idempotence et écritures concurrentes

Les retries réseau, c'est la plaie. Un client envoie un POST /payments, la connexion coupe avant la réponse, il renvoie. Deux paiements créés.

Les clés d'idempotence

La solution que je déploie désormais par défaut : un en-tête Idempotency-Key fourni par le client. Le serveur stocke la clé pendant 24 heures. Si la même clé revient, il renvoie la réponse mise en cache au lieu de retraiter.

Simple à décrire, plus subtil à implémenter. Attention aux conditions de course : la clé doit être réservée avant le traitement métier, sinon deux requêtes simultanées passent toutes les deux la vérification.

ETags et verrouillage optimiste

Pour éviter qu'un utilisateur écrase les modifications d'un autre, l'ETag est l'outil naturel. Le serveur renvoie un ETag sur GET, le client le renvoie dans If-Match sur le PUT. Si l'ETag a changé, réponse 412 Precondition Failed.

J'ai introduit ça sur un back-office où deux commerciaux pouvaient éditer la même fiche client. Les conflits d'écriture sont passés de plusieurs par semaine à zéro, avec un message d'erreur clair pour l'utilisateur au lieu d'une écrasement silencieux.

Sécurité et observabilité : ce qu'on oublie trop souvent

Deux angles sont systématiquement absents des tutos sur les API REST. Et pourtant, ce sont eux qui font la différence entre une API de démo et une API en production.

Authentification et limitation de débit

OAuth2 avec des scopes précis, pas un token unique qui donne tous les droits. Les tokens JWT doivent avoir une durée de vie courte, avec un mécanisme de refresh. Et surtout : vérifiez la signature côté serveur, ne faites jamais confiance au contenu décodé sans validation.

Le rate limiting, ensuite. Un client qui boucle à cause d'un bug peut saturer votre infrastructure en quelques minutes. Les en-têtes X-RateLimit-Limit, X-RateLimit-Remaining et Retry-After rendent la limitation explicite et permettent au client de s'adapter proprement au lieu d'échouer.

Observabilité : ce qu'on trace

Un identifiant de corrélation dans chaque réponse, propagé dans les logs et les traces. Sans lui, corréler une plainte utilisateur avec une erreur côté serveur devient un jeu de piste.

Et une spécification OpenAPI maintenue à jour. Je sais, ça paraît fastidieux. Mais c'est le seul document qui reste vrai quand le code évolue. Générer la doc depuis le code est plus fiable qu'un fichier Markdown qui dérive lentement jusqu'à devenir un mensonge.

Ce qui distingue une API qui dure

Une API REST bien conçue ne se remarque pas. Elle fait ce qu'on attend d'elle, elle renvoie les bons codes, elle supporte les retries sans créer de doublons, et elle prévient ses clients avant de casser quelque chose. C'est moins glamour qu'un système d'événements temps réel, mais ça traverse les années.

La prochaine fois que vous concevez un endpoint, posez-vous une seule question : si un client que je ne connais pas appelle cette route dans trois ans, avec un retry automatique et un cache agressif, est-ce que ça tient ? Si la réponse est oui, vous êtes probablement sur la bonne voie.

Solène Lefèvre

Solène Lefèvre

Solène Lefèvre est une spécialiste reconnue en sécurité des systèmes d'information, en cryptographie appliquée et en audit de vulnérabilités. Elle accompagne depuis plusieurs années des organisations dans l'évaluation de leurs risques techniques et la mise en place de défenses robustes. Sa pédagogie et sa rigueur lui permettent de vulgariser des sujets complexes auprès de publics variés.

Voir tous les articles →

Articles similaires