Aller au contenu
Serveur Counter.
Développement

Développer une API REST performante : les bons choix

Découvrez comment créer une API REST performante pour une application web grâce à une architecture backend optimisée et sécurisée.

Une API REST lente l’est rarement à cause du langage ou du serveur. Elle l’est à cause de quelques décisions de conception prises au début et devenues impossibles à corriger ensuite : la façon de paginer, la granularité des réponses, et l’absence de cache. Voici ces décisions, avec les codes et les en-têtes qui les mettent en œuvre, et les erreurs qui se paient le plus cher.

Les verbes ont un contrat, et il est contraignant

Choisir un verbe HTTP n’est pas une convention de style : chacun promet un comportement dont dépendent les caches, les serveurs mandataires et les mécanismes de réessai.

VerbeModifie les donnéesRejouable sans effet supplémentaire
GETNonOui
PUTOuiOui
DELETEOuiOui
POSTOuiNon

La dernière ligne est la source d’incidents les plus courante. Un client dont la connexion coupe après l’envoi mais avant la réponse ne sait pas si l’opération a eu lieu. S’il réessaie, il crée un doublon : commande passée deux fois, paiement enregistré deux fois.

La parade tient en une ligne : acceptez une clé d’idempotence fournie par le client dans un en-tête. Vous mémorisez la réponse associée à cette clé, et un second envoi portant la même clé renvoie la première réponse au lieu de recommencer. C’est indispensable dès qu’une opération a une conséquence financière.

Les codes de retour, et les deux qu’on confond

  • 200 pour une lecture réussie, 201 pour une création, accompagné de l’adresse de la ressource créée, 204 quand il n’y a rien à renvoyer.
  • 400 quand la requête est malformée, 422 quand elle est bien formée mais que son contenu est invalide. La distinction aide énormément le développeur qui consomme l’interface.
  • 409 pour un conflit, typiquement une modification concurrente.
  • 429 quand le client dépasse le débit autorisé, accompagné d’un en-tête indiquant dans combien de temps réessayer.

Les deux codes systématiquement confondus sont le 401 et le 403. Le premier signifie « je ne sais pas qui vous êtes », et invite donc à s’authentifier. Le second signifie « je sais qui vous êtes, et vous n’avez pas le droit ». Répondre 401 à un utilisateur authentifié le pousse à se reconnecter en boucle sans jamais comprendre.

La pagination : le choix qui se paie plus tard

Presque toutes les interfaces paginent par numéro de page. C’est simple, c’est intuitif, et c’est faux dès que les données bougent.

Pendant qu’un client parcourt les pages, des éléments sont ajoutés ou supprimés. Le décalage fait glisser les résultats : certains apparaissent deux fois, d’autres ne sont jamais lus. Sur un traitement par lots, cela produit des oublis silencieux, découverts des mois plus tard.

La pagination par curseur corrige le problème : au lieu de demander « la page 12 », le client demande « ce qui vient après cet élément ». Les insertions et suppressions n’affectent plus le parcours. Le coût est une contrainte, on ne peut plus sauter directement à une page arbitraire, et c’est presque toujours acceptable.

Deuxième règle, non négociable : plafonnez la taille de page côté serveur. Une interface qui accepte une limite fournie par le client accepte aussi qu’on lui demande cent mille éléments d’un coup, et c’est le moyen le plus simple de la faire tomber.

Le problème qui tue vraiment les performances

Une liste de cent éléments, et pour chacun une requête supplémentaire en base pour aller chercher son auteur ou sa catégorie. L’interface exécute alors cent une requêtes là où deux suffiraient.

C’est de loin la première cause de lenteur d’une interface, et elle est invisible en développement où la base contient dix lignes. Elle n’apparaît qu’en production, où elle s’aggrave proportionnellement au succès.

Le remède est de charger les données liées en une seule requête groupée. Le diagnostic, lui, passe par le comptage des requêtes exécutées par appel : c’est la mesure à afficher en développement, bien avant le temps de réponse. Les mêmes principes s’appliquent à un site, comme nous le détaillons dans notre article sur l’optimisation des performances sans changer d’hébergement.

Faire travailler le cache

Une interface qui recalcule à chaque appel une réponse qui n’a pas changé gaspille la ressource la plus chère dont elle dispose.

ETag: "a1b2c3"
If-None-Match: "a1b2c3"   ->   304 Not Modified

Le serveur joint une empreinte à chaque réponse. Au rappel suivant, le client la renvoie ; si rien n’a changé, le serveur répond par un code de non-modification, sans corps. Le gain porte à la fois sur le calcul et sur les données transférées, et il est considérable sur une application mobile.

Cette empreinte sert aussi à éviter l’écrasement concurrent : un client qui modifie une ressource peut préciser l’empreinte dont il dispose, et le serveur refuse si elle a changé entre-temps.

Prévoir la suite dès la première version

  • Versionnez dès le départ. Ajouter une version à une interface déjà consommée impose de gérer deux comportements. Le faire au premier jour ne coûte rien.
  • N’exposez que ce qui est nécessaire. Renvoyer l’intégralité d’un enregistrement par confort crée une dépendance sur des champs internes que vous ne pourrez plus renommer.
  • Limitez le débit par client. Sans plafond, un seul consommateur mal écrit dégrade le service pour tous les autres.
  • Journalisez par appel : durée, nombre de requêtes en base, code retourné. Sans cela, un ralentissement ne se diagnostique pas, comme nous l’expliquons dans notre article sur le monitoring des pannes.

Sur la sécurité, enfin, une interface est un service exposé comme un autre : authentification solide, chiffrement du transport, validation stricte des entrées. Les mesures sont celles décrites dans notre article sur la sécurité d’un serveur, et un pare-feu ne les remplacera pas, comme le rappelle notre article sur le rôle des pare-feu.

La réserve : REST n’est pas toujours la réponse

Le style REST convient très bien à des ressources identifiables que l’on lit et que l’on modifie. Il convient mal à deux cas fréquents, et s’entêter coûte cher.

Le premier est celui des clients qui ont chacun besoin d’un assemblage différent de données : ils multiplient les appels ou reçoivent bien plus que nécessaire. Une application mobile et un tableau de bord interne n’ont presque jamais les mêmes besoins sur les mêmes ressources.

Le second est celui des échanges continus, notification en temps réel ou flux d’événements, pour lesquels interroger en boucle gaspille des ressources des deux côtés et introduit toujours un retard. D’autres approches existent pour ces situations, et rien n’interdit de les mélanger dans une même application : une interface REST pour les ressources, un canal dédié pour les événements.

Questions fréquentes sur la conception d'une API REST


Quelle différence entre les codes 401 et 403 ?

Le 401 signifie que le client n’est pas authentifié et doit fournir des identifiants. Le 403 signifie qu’il est identifié mais n’a pas le droit d’accéder à la ressource. Répondre 401 à un utilisateur authentifié le fait se reconnecter en boucle.


Comment éviter les doublons sur une API REST ?

En acceptant une clé d’idempotence fournie par le client dans un en-tête. Le serveur mémorise la réponse associée et renvoie la même en cas de second envoi, ce qui évite qu’une coupure réseau ne provoque une double commande.


Pagination par page ou par curseur ?

Par curseur dès que les données évoluent. Avec une pagination par numéro de page, les ajouts et suppressions font glisser les résultats : certains éléments apparaissent deux fois et d’autres ne sont jamais lus.


Qu'est-ce qui ralentit le plus une API ?

Les requêtes en base répétées pour chaque élément d’une liste. Cent éléments peuvent déclencher cent une requêtes au lieu de deux. Le défaut est invisible en développement et s’aggrave proportionnellement au succès.


À quoi sert l'en-tête ETag ?

À éviter de renvoyer une réponse inchangée. Le serveur joint une empreinte, le client la renvoie au rappel suivant, et si rien n’a changé le serveur répond sans corps. La même empreinte sert à refuser une modification concurrente.