# Chapitre 2 - Les principes REST

# Les 6 contraintes REST

**REST** (*REpresentational State Transfer*) n'est ni un protocole ni une norme : c'est un **style d'architecture**, décrit par Roy Fielding en 2000. Une API est dite **RESTful** quand elle respecte ses contraintes.

Pour les illustrer, on reprend l'analogie du restaurant, et l'API publique de GitHub.

REST est décrit par 6 contraintes : [article Wikipédia](https://fr.wikipedia.org/wiki/Representational_state_transfer#Contraintes_architecturales).

### 1 - Client-serveur

Le client (l'interface) et le serveur (les données, la logique) sont séparés et évoluent indépendamment. Tant que le contrat est respecté, on peut refaire tout le front sans toucher au back, ou l'inverse.

<p class="callout info">Au restaurant : vous ne passez pas en cuisine. Le chef peut changer de four sans que vous changiez votre façon de commander.</p>

### 2 - Sans état (*stateless*)

Chaque requête contient **tout** ce qu'il faut pour être traitée. Le serveur ne garde aucun contexte de session entre deux requêtes.

```bash
curl -H "Authorization: Bearer mon-token" https://api.github.com/user
```

<p class="callout success">Au restaurant : le serveur a une mémoire de poisson rouge. Rappelez-lui votre numéro de table à chaque commande.</p>

Avantage : n'importe quel serveur peut répondre à n'importe quelle requête, ce qui facilite la montée en charge.

### 3 - Cache

Les réponses indiquent si elles peuvent être mises en cache, et combien de temps. Le client (ou un proxy) peut alors réutiliser une réponse sans redemander.

```bash
curl -i https://api.github.com/users/thibaudfrichet
```

```http
HTTP/2 200
cache-control: public, max-age=60, s-maxage=60
```

Ici, GitHub autorise à garder la réponse 60 secondes.

<p class="callout success">Au restaurant : la carte ne change pas toutes les 5 minutes. Pas besoin de la redemander à chaque fois.</p>

### 4 - Interface uniforme

La contrainte centrale de REST. Toutes les ressources se manipulent de la même manière :

- chaque ressource est **identifiée** par une URI : `/users/thibaudfrichet`, `/repos/thibaudfrichet/homelab-agent` ;
- on la manipule à travers ses **représentations** (JSON, XML…) ;
- les messages sont **autodescriptifs** : méthode, `Content-Type`, code de statut ;
- les réponses contiennent des **liens** vers les actions possibles : c'est **HATEOAS**, vu au chapitre 9.

Conséquence : quand on connaît une API REST, on sait déjà à moitié utiliser toutes les autres.

### 5 - Système en couches

Le client ne sait pas (et n'a pas besoin de savoir) s'il parle directement au serveur ou à un intermédiaire : reverse proxy, load balancer, CDN, cache, passerelle d'API…

### 6 - Code à la demande (facultatif)

Le serveur peut envoyer du code exécutable au client, typiquement du JavaScript. C'est la seule contrainte facultative, et la moins utilisée dans les API.

<p class="callout warning">La plupart des API qui se disent « REST » n'en respectent pas toutes les contraintes, en particulier HATEOAS. Fielding s'en plaint publiquement depuis 2008. Dans la pratique, « API REST » veut souvent dire « API HTTP + JSON avec des URL propres ».</p>

# Ressources, URI et représentations

### Les ressources

Une **ressource** est tout ce qui mérite d'avoir un nom et une adresse : un utilisateur, un dépôt, un livre, un emprunt, le profil de l'utilisateur connecté…

<p class="callout info">Une ressource n'est pas une table SQL. `/user` sur GitHub est une ressource (« moi »), alors qu'elle correspond à une ligne parmi d'autres de la table des utilisateurs. À l'inverse, un emprunt de livre peut réunir des données de plusieurs tables.</p>

### Les URI

Chaque ressource est identifiée par une **URI** (*Uniform Resource Identifier*). L'API de GitHub en donne de bons exemples :

<table id="bkmrk-typeexemplesignifica"><thead><tr><th>Type</th><th>Exemple</th><th>Signification</th></tr></thead><tbody><tr><td>Collection</td><td>`/users`</td><td>Tous les utilisateurs</td></tr><tr><td>Élément</td><td>`/users/thibaudfrichet`</td><td>L'utilisateur `thibaudfrichet`</td></tr><tr><td>Sous-collection</td><td>`/users/thibaudfrichet/repos`</td><td>Les dépôts de `thibaudfrichet`</td></tr><tr><td>Singleton</td><td>`/user`</td><td>L'utilisateur connecté (il n'y en a qu'un)</td></tr></tbody></table>

L'URI désigne **quoi**. C'est la méthode HTTP qui dit **quoi faire** avec. Les règles de nommage sont détaillées au chapitre 5.

```text
GET    /v1/livres/3              ✅ lire le livre 3
DELETE /v1/livres/3              ✅ supprimer le livre 3
GET    /v1/deleteLivre?id=3      ❌ un verbe dans l'URL
```

### Les représentations

Le client ne reçoit jamais la ressource elle-même, mais une **représentation** de son état à un instant donné. Une même ressource peut avoir plusieurs représentations.

Le client indique ce qu'il veut avec le header `Accept`. C'est la **négociation de contenu**. Exemple avec l'API d'une bibliothèque :

**En JSON :**

```bash
curl -H "Accept: application/json" https://api.biblio.example/v1/livres/3
```

```json
{"id": 3, "titre": "Le Petit Prince", "auteur": "Antoine de Saint-Exupéry"}
```

**En XML :**

```bash
curl -H "Accept: application/xml" https://api.biblio.example/v1/livres/3
```

```xml
<livre id="3">
  <titre>Le Petit Prince</titre>
  <auteur>Antoine de Saint-Exupéry</auteur>
</livre>
```

Et si le format demandé n'existe pas, le serveur répond `406 Not Acceptable` :

```bash
curl -i -H "Accept: image/png" https://api.biblio.example/v1/livres/3
```

```http
HTTP/1.1 406 Not Acceptable
```

<p class="callout success">Une représentation, c'est comme une photo : la même personne peut être en couleur ou en noir et blanc, en portrait ou en miniature. La personne, elle, ne change pas.</p>

Dans l'autre sens, c'est le header `Content-Type` qui indique le format du body que le client **envoie**.

# Les verbes HTTP

Les verbes (ou **méthodes**) HTTP indiquent l'action à effectuer sur la ressource désignée par l'URI.

Tous les exemples de cette page fonctionnent avec [JSONPlaceholder](https://jsonplaceholder.typicode.com), une fausse API de blog : essayez-les avec `curl -i` !

<p class="callout info">JSONPlaceholder fait semblant : il répond comme si l'opération avait réussi, mais rien n'est réellement créé ni supprimé. Parfait pour s'entraîner sans rien casser.</p>

### Les verbes à connaître

<table id="bkmrk-verber%C3%B4leexemplebody"><thead><tr><th>Verbe</th><th>Rôle</th><th>Exemple</th><th>Body</th></tr></thead><tbody><tr><td>`GET`</td><td>Lire</td><td>`GET /posts`</td><td>Non</td></tr><tr><td>`POST`</td><td>Créer dans une collection, ou déclencher un traitement</td><td>`POST /posts`</td><td>Oui</td></tr><tr><td>`PUT`</td><td>Remplacer entièrement (ou créer à une URI connue)</td><td>`PUT /posts/1`</td><td>Oui</td></tr><tr><td>`PATCH`</td><td>Modifier partiellement</td><td>`PATCH /posts/1`</td><td>Oui</td></tr><tr><td>`DELETE`</td><td>Supprimer</td><td>`DELETE /posts/1`</td><td>Non</td></tr><tr><td>`HEAD`</td><td>Comme GET, mais seulement les headers</td><td>`HEAD /posts`</td><td>Non</td></tr><tr><td>`OPTIONS`</td><td>Lister les méthodes autorisées</td><td>`OPTIONS /posts/1`</td><td>Non</td></tr></tbody></table>

```bash
curl -i https://jsonplaceholder.typicode.com/posts/1
curl -i -X DELETE https://jsonplaceholder.typicode.com/posts/1
curl -I https://jsonplaceholder.typicode.com/posts     # -I envoie un HEAD
curl -i -X OPTIONS https://jsonplaceholder.typicode.com/posts/1
```

<p class="callout info">En principe, `OPTIONS` renvoie la liste des méthodes dans le header `Allow`. JSONPlaceholder la renvoie dans `Access-Control-Allow-Methods`, un header lié à CORS (voir chapitre 8).</p>

### Sûr et réexécutable

- **Sûr (*safe*) :** la requête ne modifie rien sur le serveur. On peut l'envoyer sans risque.
- **Réexécutable :** envoyer la requête 1 fois ou 10 fois laisse le serveur dans le même état. On peut la renvoyer sans risque si le réseau a coupé.

<table id="bkmrk-verbes%C3%BBridempotent-g"><thead><tr><th>Verbe</th><th>Sûr</th><th>Réexécutable</th></tr></thead><tbody><tr><td>`GET`, `HEAD`, `OPTIONS`</td><td>✅</td><td>✅</td></tr><tr><td>`PUT`</td><td>❌</td><td>✅</td></tr><tr><td>`DELETE`</td><td>❌</td><td>✅</td></tr><tr><td>`POST`</td><td>❌</td><td>❌</td></tr><tr><td>`PATCH`</td><td>❌</td><td>❌ (pas garanti)</td></tr></tbody></table>

<p class="callout success">Cliquer 3 fois sur « Payer » envoie 3 `POST` : 3 paiements (et un client furieux).  
Supprimer 3 fois l'article 1 avec `DELETE`, c'est toujours un seul article supprimé.</p>

<p class="callout info">Réexécutable ne veut pas dire « même réponse » : le premier `DELETE` renvoie `204`, le second peut renvoyer `404`. Ce qui compte, c'est l'état du serveur.</p>

### PUT ou PATCH ?

`PUT` **remplace** toute la ressource : les champs absents sont perdus.

```bash
curl -i -X PUT https://jsonplaceholder.typicode.com/posts/1 \
  -H "Content-Type: application/json" \
  -d '{"id": 1, "userId": 1, "title": "Nouveau titre", "body": "Nouveau contenu"}'
```

`PATCH` ne modifie **que** les champs envoyés.

```bash
curl -i -X PATCH https://jsonplaceholder.typicode.com/posts/1 \
  -H "Content-Type: application/json" \
  -d '{"title": "Nouveau titre"}'
```

Avec un `PUT` qui ne contiendrait que `{"title": "Nouveau titre"}`, l'article perdrait son contenu.

### Ne jamais modifier avec GET

<p class="callout warning">Un `GET` doit rester **sûr**. Les navigateurs, les robots d'indexation et les outils de préchargement suivent les liens sans demander la permission.</p>

```text
❌ GET    /api/article/delete?id=1
✅ DELETE /posts/1
```

En 2005, le Google Web Accelerator préchargeait les liens des pages visitées… y compris les liens « supprimer » de certaines interfaces d'administration.  
[https://www.lemondeinformatique.fr/actualites/lire-retour-au-stand-pour-google-web-accelerator-8289.html](https://www.lemondeinformatique.fr/actualites/lire-retour-au-stand-pour-google-web-accelerator-8289.html)

# Les codes de statut

Le code de statut est la première chose qu'un client regarde. Un bon code évite au client de devoir lire le body pour savoir si ça a marché.

<p class="callout warning">Une API qui répond toujours `200 OK` avec `{"error": true}` dans le body oblige chaque client à réinventer la gestion d'erreurs. Le code de statut est fait pour ça : utilisez-le.</p>

Les exemples ci-dessous concernent l'API d'une bibliothèque en ligne : des livres, des membres, des emprunts.

### 2xx - Succès

<table id="bkmrk-codequand-l%27utiliser"><thead><tr><th>Code</th><th>Quand l'utiliser</th><th>Exemple</th></tr></thead><tbody><tr><td>`200 OK`</td><td>Succès avec un body</td><td>`GET /v1/livres`</td></tr><tr><td>`201 Created`</td><td>Ressource créée. Ajouter le header `Location`</td><td>`POST /v1/emprunts`</td></tr><tr><td>`204 No Content`</td><td>Succès sans body</td><td>`DELETE /v1/emprunts/42`</td></tr></tbody></table>

### 3xx - Redirection

<table id="bkmrk-codequand-l%27utiliser-1"><thead><tr><th>Code</th><th>Quand l'utiliser</th></tr></thead><tbody><tr><td>`301 Moved Permanently`</td><td>La ressource a changé d'adresse définitivement</td></tr><tr><td>`304 Not Modified`</td><td>La version en cache du client est toujours bonne</td></tr></tbody></table>

### 4xx - Erreur du client

<table id="bkmrk-codequand-l%27utiliser-2"><thead><tr><th>Code</th><th>Quand l'utiliser</th><th>Exemple</th></tr></thead><tbody><tr><td>`400 Bad Request`</td><td>Requête mal formée</td><td>Un body qui n'est pas du JSON valide</td></tr><tr><td>`401 Unauthorized`</td><td>Pas authentifié, ou authentification invalide</td><td>`GET /v1/me` sans token</td></tr><tr><td>`403 Forbidden`</td><td>Authentifié, mais pas autorisé</td><td>Un membre qui veut supprimer un livre du catalogue</td></tr><tr><td>`404 Not Found`</td><td>La ressource n'existe pas</td><td>`GET /v1/livres/999999`</td></tr><tr><td>`405 Method Not Allowed`</td><td>Méthode non supportée sur cette URI. Ajouter le header `Allow`</td><td>`DELETE /v1/livres` (supprimer tout le catalogue)</td></tr><tr><td>`406 Not Acceptable`</td><td>Format demandé dans `Accept` indisponible</td><td>`Accept: image/png`</td></tr><tr><td>`409 Conflict`</td><td>Conflit avec l'état actuel de la ressource</td><td>Emprunter un livre déjà emprunté</td></tr><tr><td>`418 I'm a teapot`</td><td>Le serveur est une théière (RFC 2324, poisson d'avril de 1998)</td><td>Demander un café à une théière</td></tr><tr><td>`422 Unprocessable Content`</td><td>Requête bien formée mais données invalides</td><td>Une date de retour antérieure à la date d'emprunt</td></tr><tr><td>`429 Too Many Requests`</td><td>Trop de requêtes. Ajouter le header `Retry-After`</td><td>Un script qui interroge l'API en boucle</td></tr></tbody></table>

### 5xx - Erreur du serveur

<table id="bkmrk-codequand-l%27utiliser-3"><thead><tr><th>Code</th><th>Quand l'utiliser</th></tr></thead><tbody><tr><td>`500 Internal Server Error`</td><td>Bug côté serveur. Ne jamais le renvoyer volontairement</td></tr><tr><td>`502 Bad Gateway`</td><td>Un intermédiaire n'a pas obtenu de réponse valide du serveur</td></tr><tr><td>`503 Service Unavailable`</td><td>Serveur indisponible (maintenance, surcharge)</td></tr></tbody></table>

### Les pièges classiques

- **401 ou 403 ?** `401` : « je ne sais pas qui tu es ». `403` : « je sais qui tu es, et c'est non ».
- **400 ou 422 ?** `400` : je n'arrive pas à lire ta requête. `422` : je l'ai lue, mais son contenu n'est pas valide. FastAPI renvoie `422` par défaut pour les erreurs de validation.
- **404 ou 403 ?** Pour une ressource qui appartient à quelqu'un d'autre, répondre `404` évite de révéler qu'elle existe. On en reparle au chapitre 8.

<p class="callout info">La liste complète est sur [MDN](https://developer.mozilla.org/fr/docs/Web/HTTP/Status). Et pour les retenir : [http.cat](https://http.cat).</p>

# Le modèle de maturité de Richardson

En 2008, [Leonard Richardson](https://fr.wikipedia.org/wiki/Mod%C3%A8le_de_maturit%C3%A9_de_Richardson) propose un modèle en 4 niveaux pour mesurer à quel point une API exploite le web. Chaque niveau ajoute un des principes REST.

[![Pyramide du modèle de maturité de Richardson](https://formation.tfrichet.fr/uploads/images/gallery/2026-10/scaled-1680-/WfTeqBNpPy82yq6p-richardson.png)](https://formation.tfrichet.fr/uploads/images/gallery/2026-10/WfTeqBNpPy82yq6p-richardson.png)

Fil conducteur : dans l'API d'une bibliothèque, **annuler la réservation 42**.

### Niveau 0 - Le marécage du POX (Plain Old XML)

Une seule URL, une seule méthode (`POST`), et l'action est décrite dans le body. HTTP ne sert que de tuyau. C'est le cas de SOAP et de XML-RPC (POX : *Plain Old XML*).

```http
POST /api
Content-Type: application/json

{"action": "cancelReservation", "reservationId": 42}
```

```http
HTTP/1.1 200 OK

{"success": false, "error": "Livre déjà retiré"}
```

### Niveau 1 - Les ressources

Chaque ressource a sa propre URL, mais on utilise encore `POST` pour tout, avec des verbes dans les URL.

```http
POST /api/reservations/42/cancel
```

### Niveau 2 - Les verbes et codes HTTP

On utilise la bonne méthode HTTP pour chaque action, et les bons codes de statut. C'est le niveau de la grande majorité des API « REST ».

```http
DELETE /v1/reservations/42
```

```http
HTTP/1.1 409 Conflict
```

### Niveau 3 - Les contrôles hypermédias

Les réponses contiennent des **liens** vers les actions possibles. Le client n'a plus besoin de deviner les URL : il suit les liens. C'est **HATEOAS**.

```json
{
  "id": 42,
  "status": "waiting",
  "_links": {
    "self":   {"href": "/v1/reservations/42"},
    "cancel": {"href": "/v1/reservations/42", "method": "DELETE"},
    "book":   {"href": "/v1/livres/3"}
  }
}
```

Si le livre a déjà été retiré, le lien `cancel` n'apparaît plus : le client sait qu'il ne peut plus annuler, sans connaître les règles métier.

<p class="callout info">Selon Fielding, seul le niveau 3 est vraiment REST. On y revient en détail au chapitre 9.</p>