05 - Body, headers et le diable dans les détails
Ce que tu vas apprendre
- Comment structurer un body JSON propre
- Les headers HTTP standards que ton API doit utiliser
- Quand et comment créer des headers custom
- Les clés d'idempotence et pourquoi elles sauvent des vies
Prerequisites
- Avoir lu l'article sur les codes de statut
- Connaitre les bases des requêtes et réponses HTTP
Le header oublie qui a coûte cher
Un collegue avait déployé une API de paiement. Tout fonctionnait en dev. En prod, les requêtes echouaient mysterieusement. Apres deux heures de debug, le problème : le reverse proxy ajoutait Content-Encoding: gzip mais le serveur Node ne decompressait pas. Le body arrivait comme du charabia. Un header manquant dans la config Express, et 500 transactions bloquees.
Les headers ne sont pas un détail. Ils sont le système nerveux de HTTP.
Le body JSON
Structure de requête
Garde tes bodies plats autant que possible. L'imbrication profonde complique la validation et la documentation.
typescript// Bon : structure plate et claire
const createUserBody = {
name: "Alice Martin",
email: "alice@example.com",
role: "editor",
};
// Acceptable : un niveau d'imbrication pour grouper
const createOrderBody = {
items: [
{ productId: "prod_123", quantity: 2 },
{ productId: "prod_456", quantity: 1 },
],
shippingAddress: {
street: "12 rue de Rivoli",
city: "Paris",
postalCode: "75001",
},
};
// Mauvais : imbrication excessive
const badBody = {
data: {
attributes: {
user: {
profile: {
name: "Alice",
},
},
},
},
};
Structure de réponse
Je recommande un format coherent pour toutes tes réponses :
typescript// Ressource unique
{
"id": 42,
"name": "Alice",
"email": "alice@example.com",
"createdAt": "2026-03-29T10:30:00Z"
}
// Collection
{
"data": [
{"id": 42, "name": "Alice"},
{"id": 43, "name": "Bob"}
],
"meta": {
"total": 156,
"page": 1,
"limit": 20
}
}
Pour les dates, utilise toujours ISO 8601 avec le fuseau horaire : 2026-03-29T10:30:00Z. Pas de timestamps Unix, pas de formats locaux.
Les headers standards indispensables
Content-Type
Dit au serveur quel format tu envoies. Dit au client quel format il recoit.
http# Requete
POST /api/users HTTP/1.1
Content-Type: application/json
# Reponse
HTTP/1.1 200 OK
Content-Type: application/json; charset=utf-8
Si ton API n'accepte que du JSON, refuse proprement les autres formats :
typescriptapp.use((req, res, next) => {
if (
req.method !== "GET" &&
req.method !== "DELETE" &&
!req.is("application/json")
) {
return res.status(415).json({
error: "Unsupported Media Type. Use application/json.",
});
}
next();
});
Accept
Le client indique quel format de réponse il préféré.
httpGET /api/users/42 HTTP/1.1
Accept: application/json
En theorie, ton API pourrait renvoyer du JSON ou du XML selon le header Accept. En pratique, la plupart des API modernes ne supportent que JSON, et c'est tres bien comme ca.
Authorization
Le header standard pour l'authentification.
httpGET /api/users/me HTTP/1.1
Authorization: Bearer eyJhbGciOiJIUzI1NiIs...
N'invente pas un header custom pour ca. Authorization est compris par tous les outils, proxies et middlewares du monde.
Cache-Control et ETag
Pour le caching des réponses GET.
httpHTTP/1.1 200 OK
Cache-Control: public, max-age=3600
ETag: "v1-abc123"
Location
Pointe vers la ressource créée apres un POST.
httpHTTP/1.1 201 Created
Location: /api/users/43
Les headers custom
Quand tu as besoin d'un header spécifique a ton application, prefixe-le avec un nom de projet (le prefixe X- est déprécié mais encore tres courant).
http# Cle d'idempotence
Idempotency-Key: 550e8400-e29b-41d4-a716-446655440000
# Rate limiting info
X-RateLimit-Limit: 100
X-RateLimit-Remaining: 42
X-RateLimit-Reset: 1711720800
# Request ID pour le tracing
X-Request-Id: req_abc123def456
Les clés d'idempotence
C'est le mecanisme le plus sous-estime en design d'API. Le problème : un client envoie un POST pour créer une commande. Le réseau coupe. Le client ne sait pas si la commande a ete créée ou non. Il renvoie la requête. Sans clé d'idempotence, tu as deux commandes.
typescriptrouter.post("/api/orders", async (req, res) => {
const idempotencyKey = req.headers["idempotency-key"];
if (!idempotencyKey) {
return res.status(400).json({
error: "Idempotency-Key header is required",
});
}
// Verifier si cette cle a deja ete utilisee
const existing = await idempotencyStore.get(idempotencyKey);
if (existing) {
// Renvoyer la meme reponse que la premiere fois
return res.status(existing.statusCode).json(existing.body);
}
// Creer la commande
const order = await orderService.create(req.body);
// Stocker la reponse associee a cette cle
await idempotencyStore.set(idempotencyKey, {
statusCode: 201,
body: order,
});
res.status(201).json(order);
});
Stripe utilise ce pattern pour toutes ses API de paiement. Si ca marche pour des milliards de dollars de transactions, ca marchera pour ton app.
La duree de vie d'une clé d'idempotence est généralement de 24 a 48 heures. Apres ca, la meme clé peut etre reutilisee.
Tu trouveras plus d'exemples de patterns de headers sur paltemps.fr.
Résumé
- JSON avec structure plate, dates en ISO 8601, format coherent entre les endpoints
Content-Type,Accept,Authorization,Location: les headers standards non negociables- Refuse les Content-Type non supportes avec un 415
- Les clés d'idempotence protegent les POST contre les doubles envois
- Les headers custom pour le rate limiting et le tracing ameliorent l'observabilité
Precedent : Les codes de statut HTTP | Suivant : La pagination
Sources
- RFC 9110 -- HTTP Semantics, Header Fields. https://www.rfc-editor.org/rfc/rfc9110#section-6
- Stripe API -- Idempotent Requests. https://stripe.com/docs/api/idempotent_requests
- IETF Draft -- The Idempotency-Key HTTP Header Field. https://datatracker.ietf.org/doc/draft-ietf-httpapi-idempotency-key-header/