API REST back-end : validation des entrées et règles métier

Capítulo 5

Temps de lecture estimé : 10 minutes

+ Exercice

Pourquoi valider : sécurité, robustesse et contrat d’API

La validation des entrées consiste à refuser (ou normaliser) toute donnée qui ne respecte pas le contrat attendu par l’API. Elle protège contre les erreurs de type/format, limite les comportements imprévus (champs inconnus, valeurs hors bornes) et rend les erreurs exploitables côté client. On distingue deux niveaux : la validation technique (forme des données) et la validation métier (cohérence fonctionnelle).

1) Validation des paramètres d’URL : path et query

1.1 Paramètres de path : type, format, bornes

Les paramètres de path identifient souvent une ressource. Ils doivent être validés très tôt, avant tout accès aux dépendances (base de données, services externes).

  • Type : entier, UUID, slug, etc.
  • Format : UUID v4, date ISO-8601, etc.
  • Contraintes : min/max, longueur, regex.

Exemples de règles :

GET /users/{userId}          userId: UUID v4 obligatoire (regex/parse UUID)  -> 400 si invalide (forme de l’URL)

1.2 Paramètres de query : optionnels, listes, valeurs autorisées

Les query params sont souvent optionnels. La validation doit couvrir :

  • Présence : requis vs optionnel (ex. from requis si to est fourni).
  • Type : entier, booléen, date, liste (ids=1,2,3 ou ids=1&ids=2).
  • Ensemble de valeurs : enum (status=active|disabled).
  • Bornes : limit min/max, page >= 1.
  • Champs inconnus : stratégie explicite (voir section dédiée).

Exemple de validation de query :

Continuez dans notre application.
  • Écoutez le fichier audio avec l'écran éteint.
  • Obtenez un certificat à la fin du programme.
  • Plus de 5000 cours à découvrir !
Ou poursuivez votre lecture ci-dessous...
Download App

Téléchargez l'application

GET /invoices?status=paid&limit=200&from=2026-01-01
  • status ∈ {draft, sent, paid}
  • limit entier, 1..100 (200 => erreur)
  • from date ISO-8601

2) Validation du corps JSON : types, formats, contraintes et champs requis

2.1 Règles de base à appliquer systématiquement

  • Content-Type : exiger application/json pour les endpoints JSON (sinon 415).
  • JSON bien formé : erreur de parsing => 400 avec message clair.
  • Type des champs : string vs number vs boolean vs object vs array.
  • Formats : email, date, UUID, URL (validation stricte).
  • Contraintes : min/max, longueur, regex, taille de tableau, unicité dans un tableau.
  • Champs requis : présents et non vides selon la règle (attention à la différence entre absent et null).
  • Gestion des champs inconnus : refuser ou ignorer, mais de façon cohérente (voir 2.3).

2.2 Exemple concret : création d’un client (POST)

Supposons un endpoint de création :

POST /customers

Corps attendu :

{  "email": "a@exemple.com",  "firstName": "Ada",  "lastName": "Lovelace",  "age": 28,  "marketingOptIn": true}

Règles techniques typiques :

  • email : requis, string, format email, longueur max 254
  • firstName : requis, string, min 1, max 100
  • lastName : requis, string, min 1, max 100
  • age : optionnel, integer, min 0, max 130
  • marketingOptIn : optionnel, boolean

Exemples d’erreurs techniques :

{  "email": "pas-un-email",  "firstName": "",  "age": -3,  "unknownField": "x"}
  • email invalide (format)
  • firstName trop court (min 1)
  • age hors borne (min 0)
  • unknownField non autorisé (si politique stricte)

2.3 Gestion des champs inconnus : choisir une politique

Deux stratégies principales :

  • Politique stricte (recommandée pour un contrat fort) : tout champ non documenté => erreur de validation. Avantage : détecte rapidement les bugs clients, évite la persistance accidentelle de données. Inconvénient : plus sensible aux clients “tolérants”.
  • Politique tolérante : ignorer les champs inconnus (ou les stocker dans une structure dédiée). Avantage : compatibilité avec des clients qui envoient plus de données. Inconvénient : risque de masquer des erreurs et d’introduire des comportements implicites.

Bonnes pratiques :

  • Être cohérent par endpoint (idéalement par API entière).
  • Si vous ignorez, envisagez de renvoyer un avertissement (sans casser le contrat) via un champ optionnel de métadonnées, ou via logs côté serveur.
  • Si vous refusez, renvoyer une erreur par champ avec un code stable (voir section 4).

3) Validation technique vs validation métier

3.1 Validation technique (forme)

Elle vérifie que la requête est “parsable” et conforme au schéma : types, formats, contraintes simples, champs requis, champs inconnus. Elle ne nécessite pas (ou très peu) d’accès à l’état du système.

3.2 Validation métier (cohérence fonctionnelle)

Elle vérifie que la commande est cohérente avec les règles fonctionnelles et l’état courant : unicité, transitions autorisées, dépendances entre champs, limites liées au compte, etc. Exemples :

  • Interdire marketingOptIn=true si email est absent (si l’email est requis pour l’opt-in).
  • Refuser une date de fin antérieure à la date de début.
  • Empêcher une mise à jour si la ressource est dans un état “verrouillé”.
  • Vérifier l’unicité d’un email (souvent nécessite une requête en base).

3.3 Ordre d’exécution recommandé

  • Étape 1 : validation technique (rapide, déterministe) : parsing JSON, types, formats, contraintes, champs requis, champs inconnus.
  • Étape 2 : normalisation (si applicable) : trim, mise en forme (ex. email en minuscules) sans modifier le sens.
  • Étape 3 : validation métier : règles dépendantes de l’état, vérifications d’unicité, transitions, cohérence inter-champs.
  • Étape 4 : exécution : persistance, appels externes.

Raison : éviter des accès coûteux (DB) si la requête est déjà invalide techniquement, et produire des erreurs plus claires (un “email invalide” est plus utile qu’un “unicité impossible”).

4) Concevoir des messages de validation exploitables

4.1 Principes : lisible, stable, actionnable

  • Liste d’erreurs par champ : permettre au client d’afficher plusieurs erreurs en une fois.
  • Codes d’erreur stables : ne pas dépendre du texte (qui peut changer/traduire). Ex. invalid_format, required, too_short, out_of_range, unknown_field, business_rule_violation.
  • Chemin du champ : utiliser une notation stable (ex. JSON Pointer /address/postalCode ou dot-notation address.postalCode).
  • Contexte : inclure expected, min, max, pattern quand utile.
  • Ne pas fuiter d’informations sensibles : éviter de révéler des détails internes (requêtes SQL, existence d’un compte si cela pose problème).

4.2 Format de réponse recommandé

Un format courant est :

  • error : catégorie globale
  • message : résumé humain
  • violations : liste structurée
{  "error": "validation_failed",  "message": "Some fields are invalid.",  "violations": [    {      "field": "email",      "code": "invalid_format",      "message": "Email must be a valid address.",      "meta": { "format": "email" }    },    {      "field": "age",      "code": "out_of_range",      "message": "Age must be between 0 and 130.",      "meta": { "min": 0, "max": 130 }    }  ]}

4.3 400 vs 422 : quand utiliser lequel

Deux approches existent ; l’important est d’être cohérent :

  • 400 Bad Request : requête mal formée ou invalide au sens du contrat (JSON invalide, types/format/contraintes, champs inconnus). Beaucoup d’APIs utilisent 400 pour toute validation d’entrée.
  • 422 Unprocessable Entity : requête bien formée techniquement, mais impossible à traiter pour des raisons sémantiques (souvent validation métier). Exemple : transition d’état interdite, incohérence inter-champs, règle fonctionnelle violée.

Recommandation pratique :

  • Utiliser 400 pour la validation technique.
  • Utiliser 422 pour la validation métier (cohérence fonctionnelle) lorsque la requête est syntaxiquement et structurellement correcte.

4.4 Exemples de réponses 400 et 422

Exemple 400 (JSON invalide ou champ inconnu) :

HTTP/1.1 400 Bad RequestContent-Type: application/json{  "error": "validation_failed",  "message": "Request body is invalid.",  "violations": [    { "field": "unknownField", "code": "unknown_field", "message": "Field is not allowed." }  ]}

Exemple 422 (règle métier) :

HTTP/1.1 422 Unprocessable EntityContent-Type: application/json{  "error": "business_rule_violation",  "message": "Request is semantically invalid.",  "violations": [    {      "field": "email",      "code": "already_in_use",      "message": "Email is already used.",      "meta": { "constraint": "customers_email_unique" }    }  ]}

5) Stratégie de validation pour PATCH (mises à jour partielles)

5.1 Problème : “requis” ne veut pas dire la même chose en PATCH

En PATCH, le client envoie un sous-ensemble de champs. Les règles changent :

  • Un champ requis à la création n’est pas forcément requis en PATCH.
  • Mais si un champ est présent dans le PATCH, il doit respecter ses contraintes (type, format, bornes).

5.2 Approche étape par étape (PATCH JSON « merge »)

Si vous utilisez un PATCH de type “merge” (corps JSON partiel), une approche robuste est :

  • Étape 1 : valider le JSON partiel : types/formats/contraintes sur les champs présents uniquement, et champs inconnus selon votre politique.
  • Étape 2 : charger l’état courant de la ressource.
  • Étape 3 : appliquer le patch (fusion) en mémoire.
  • Étape 4 : valider le résultat final : s’assurer que l’objet complet respecte les invariants (techniques et métier). Exemple : après patch, un champ requis ne doit pas devenir vide si la règle l’interdit.
  • Étape 5 : persister.

Cette double validation évite un piège classique : un patch qui semble valide isolément mais rend l’objet final incohérent.

5.3 Gestion des valeurs null : absent vs null

Décidez explicitement de la sémantique :

  • Champ absent : ne pas modifier.
  • Champ présent avec valeur non-null : mettre à jour.
  • Champ présent avec null : deux options cohérentes :
  • Option A (null = effacer) : autoriser null uniquement pour les champs “nullable”. Pour les champs non-nullables, renvoyer une violation not_nullable.
  • Option B (null interdit) : exiger que l’effacement passe par une action explicite (ex. endpoint dédié, ou JSON Patch avec opération remove). Dans ce cas, null => erreur invalid_type ou null_not_allowed.

Exemple (Option A) :

PATCH /customers/{id}{  "age": null}

Si age est nullable : OK (effacement). Si non : 422 ou 400 selon si vous considérez cela technique (schéma) ou métier (règle).

5.4 PATCH et champs inconnus

En PATCH, refuser les champs inconnus est souvent préférable : un client qui tente de mettre à jour un champ inexistant doit être alerté. Sinon, il peut croire que la mise à jour a été prise en compte.

6) Compatibilité lors d’ajouts de champs : éviter de casser les clients

6.1 Ajout de champs côté API : règles de compatibilité

  • Ajouter un champ optionnel est généralement rétrocompatible.
  • Ajouter un champ requis casse les clients existants (ils ne l’enverront pas). Préférer une phase transitoire : d’abord optionnel, puis requis dans une nouvelle version/contrat.
  • Ajouter un champ avec valeur par défaut : utile pour migration progressive, mais documenter la valeur par défaut et la logique associée.

6.2 Politique “champs inconnus” et compatibilité

La compatibilité dépend aussi de votre politique :

  • Si votre API refuse les champs inconnus, un client qui envoie un champ “futur” échouera sur un serveur “ancien”. C’est acceptable si vous contrôlez les clients ou versionnez strictement.
  • Si votre API ignore les champs inconnus, un client peut envoyer des champs supplémentaires sans casser les serveurs plus anciens, mais vous perdez la détection d’erreurs.

Compromis fréquent : stricte sur les endpoints d’écriture (POST/PUT/PATCH), plus tolérante sur certains endpoints de lecture si nécessaire, tout en restant cohérent et documenté.

7) Check-list d’implémentation (pratique)

7.1 À l’entrée de chaque endpoint

  • Valider Content-Type et parser le JSON (si applicable).
  • Valider path params (type/format) avant toute logique.
  • Valider query params : types, bornes, enums, dépendances simples.
  • Valider le body : schéma, champs requis, contraintes, champs inconnus.

7.2 Avant d’exécuter la commande

  • Normaliser (trim, minuscules) si vous le faites.
  • Valider métier : cohérence inter-champs, état courant, unicité, transitions.
  • Construire une réponse d’erreurs structurée : violations[] avec field + code + message + meta.

7.3 Pour PATCH

  • Valider uniquement les champs présents (forme).
  • Appliquer le patch sur l’état courant.
  • Revalider l’objet final (invariants) et les règles métier.
  • Définir la sémantique de null (effacement vs interdit) et s’y tenir.

Répondez maintenant à l’exercice sur le contenu :

Dans une API REST, quelle situation correspond le mieux à une réponse 422 plutôt qu’à 400 lors de la validation d’une requête ?

Tu as raison! Félicitations, passez maintenant à la page suivante

Vous avez raté! Essayer à nouveau.

Le code 422 s’applique quand la requête est bien formée et conforme au schéma, mais ne peut pas être traitée pour des raisons sémantiques (validation métier : unicité, cohérence inter-champs, transitions). Les erreurs de parsing, de schéma ou de contrat relèvent plutôt de 400 (et 415 pour le Content-Type).

Chapitre suivant

API REST back-end : gestion d’erreurs propre et traçabilité

Arrow Right Icon
Couverture de livre électronique gratuite Développement back-end : concevoir une API REST propre
63%

Développement back-end : concevoir une API REST propre

Nouveau cours

8 pages

Téléchargez l'application pour obtenir une certification gratuite et écouter des cours en arrière-plan, même avec l'écran éteint.