Développement Web

API REST : comprendre et utiliser les API pour intégrer des services

13 min de lecture

Ce que vous saurez faire à la fin

  1. Comprendre les principes REST en 10 minutes
  2. Consommer une API publique depuis terminal, Postman, Node
  3. Gérer authentification, pagination, erreurs
  4. Construire un petit serveur REST Express en 20 lignes
  5. Tester et documenter votre API

Durée : 2 heures. Pré-requis : Node.js 20+, connaissance JSON.

Étape 1 — Vocabulaire REST

Ressource   : "client", "facture", "produit" (nom commun pluriel)
Endpoint    : URL associée, ex: /clients, /clients/42
Verbe HTTP  : action sur la ressource
  GET    lire
  POST   créer
  PUT    remplacer entièrement
  PATCH  modifier partiellement
  DELETE supprimer
Status code : réponse serveur (200 OK, 404 Not Found, 500 Error...)

Étape 2 — Tester une API publique avec curl

# API JSONPlaceholder (gratuite, pour tests)
curl https://jsonplaceholder.typicode.com/users/1

# Lire avec jq (parseur JSON)
curl -s https://jsonplaceholder.typicode.com/users/1 | jq .

# Créer
curl -X POST https://jsonplaceholder.typicode.com/posts \
  -H "Content-Type: application/json" \
  -d '{"title":"Mon post","body":"Contenu","userId":1}'

# Supprimer
curl -X DELETE https://jsonplaceholder.typicode.com/posts/1

Étape 3 — Postman pour les tests interactifs

  1. Téléchargez Postman (postman.com/downloads).
  2. Créez une collection « Test API ».
  3. Ajoutez une requête : GET https://jsonplaceholder.typicode.com/users.
  4. Onglet Headers : ajoutez Accept: application/json si besoin.
  5. Send : vous voyez la réponse, status code, temps, taille.
  6. Sauvegardez comme « List users ».
  7. Dupliquez pour GET/POST/PUT/DELETE. Votre collection devient un cahier de tests.

Étape 4 — Client JavaScript avec fetch

const API = "https://jsonplaceholder.typicode.com";

async function getUser(id) {
  const r = await fetch(`${API}/users/${id}`);
  if (!r.ok) throw new Error(`HTTP ${r.status}`);
  return r.json();
}

async function creerPost(data) {
  const r = await fetch(`${API}/posts`, {
    method: "POST",
    headers: { "Content-Type": "application/json" },
    body: JSON.stringify(data),
  });
  return r.json();
}

// Utilisation
const user = await getUser(1);
console.log(user.name);

const post = await creerPost({ title: "Salut", body: "Test", userId: 1 });
console.log(post.id);

Étape 5 — Gérer l’authentification

// Token dans le header Authorization
const TOKEN = process.env.API_TOKEN;

async function getFactures() {
  const r = await fetch("https://api.example.sn/v1/factures", {
    headers: { "Authorization": `Bearer ${TOKEN}` },
  });
  if (r.status === 401) throw new Error("Token invalide");
  if (!r.ok) throw new Error(`HTTP ${r.status}`);
  return r.json();
}
# Avec curl
curl -s "https://api.example.sn/v1/factures" \
  -H "Authorization: Bearer $TOKEN" \
  | jq '.data[] | {numero, montant}'

Étape 6 — Pagination

async function* paginerTous(url) {
  let next = url;
  while (next) {
    const r = await fetch(next).then(r => r.json());
    yield* r.data;
    next = r.links?.next;
  }
}

// Usage
for await (const client of paginerTous("https://api.example.sn/v1/clients?limit=100")) {
  console.log(client.nom);
}

Étape 7 — Retries et backoff

async function withRetry(fn, max = 3) {
  for (let i = 0; i < max; i++) {
    try {
      return await fn();
    } catch (e) {
      if (e.status === 429 || (e.status >= 500 && e.status < 600)) {
        const backoff = 2 ** i * 500 + Math.random() * 300;
        await new Promise(r => setTimeout(r, backoff));
        continue;
      }
      throw e;
    }
  }
  throw new Error("Max retries dépassé");
}

const data = await withRetry(() => getFactures());

Étape 8 — Idempotence pour POSTs critiques

import { randomUUID } from "crypto";

await fetch("https://api.example.sn/v1/paiements", {
  method: "POST",
  headers: {
    "Idempotency-Key": randomUUID(),   // unique par paiement
    "Content-Type": "application/json",
    "Authorization": `Bearer ${TOKEN}`,
  },
  body: JSON.stringify({ montant: 150000, client_id: 42 }),
});

Étape 9 — Construire votre première API avec Express

mkdir mon-api && cd mon-api
npm init -y
npm install express
// server.js
import express from "express";
const app = express();
app.use(express.json());

const clients = new Map();
let seq = 1;

// Créer
app.post("/v1/clients", (req, res) => {
  if (!req.body.nom) return res.status(400).json({ error: "nom requis" });
  const id = String(seq++);
  const c = { id, nom: req.body.nom, ville: req.body.ville || "" };
  clients.set(id, c);
  res.status(201).location(`/v1/clients/${id}`).json(c);
});

// Lister
app.get("/v1/clients", (_req, res) => {
  res.json({ data: [...clients.values()] });
});

// Lire un
app.get("/v1/clients/:id", (req, res) => {
  const c = clients.get(req.params.id);
  return c ? res.json(c) : res.status(404).json({ error: "not_found" });
});

// Modifier
app.patch("/v1/clients/:id", (req, res) => {
  const c = clients.get(req.params.id);
  if (!c) return res.status(404).json({ error: "not_found" });
  Object.assign(c, req.body);
  res.json(c);
});

// Supprimer
app.delete("/v1/clients/:id", (req, res) => {
  clients.delete(req.params.id);
  res.status(204).end();
});

app.listen(3000, () => console.log("API http://localhost:3000"));
node server.js
# Tester:
curl -X POST http://localhost:3000/v1/clients \
  -H "Content-Type: application/json" \
  -d '{"nom":"SARL Dakar","ville":"Dakar"}'

curl http://localhost:3000/v1/clients

Étape 10 — Validation avec Zod

npm install zod
import { z } from "zod";

const CreerClient = z.object({
  nom: z.string().min(2).max(100),
  ville: z.string().optional(),
  email: z.string().email().optional(),
});

app.post("/v1/clients", (req, res) => {
  const result = CreerClient.safeParse(req.body);
  if (!result.success) {
    return res.status(422).json({ errors: result.error.errors });
  }
  // ... use result.data
});

Étape 11 — CORS

npm install cors
import cors from "cors";

app.use(cors({
  origin: ["https://app.example.sn"],
  credentials: true,
}));

Étape 12 — Rate limiting

npm install express-rate-limit
import { rateLimit } from "express-rate-limit";

app.use("/v1/", rateLimit({
  windowMs: 60_000,
  max: 120,
  standardHeaders: true,
  legacyHeaders: false,
}));

Étape 13 — Tests avec Supertest

npm install -D supertest vitest
// server.test.js
import request from "supertest";
import app from "./server.js";

test("POST /v1/clients crée un client", async () => {
  const r = await request(app)
    .post("/v1/clients")
    .send({ nom: "Test" })
    .expect(201);
  expect(r.body).toHaveProperty("id");
});

test("GET /v1/clients/:id inexistant renvoie 404", async () => {
  await request(app).get("/v1/clients/9999").expect(404);
});

Étape 14 — Documenter avec OpenAPI

npm install swagger-ui-express swagger-jsdoc
import swaggerUi from "swagger-ui-express";
import swaggerJsdoc from "swagger-jsdoc";

const swaggerSpec = swaggerJsdoc({
  definition: {
    openapi: "3.0.0",
    info: { title: "Mon API", version: "1.0.0" },
  },
  apis: ["./server.js"],   // lit les commentaires JSDoc
});

app.use("/docs", swaggerUi.serve, swaggerUi.setup(swaggerSpec));
// Ouvrez http://localhost:3000/docs

Étape 15 — Checklist

✓ Noms de ressources au pluriel
✓ Verbes HTTP cohérents
✓ Codes retour standards (201, 204, 404, 422)
✓ Validation Zod sur toutes les entrées
✓ CORS configuré correctement
✓ Rate limiting en place
✓ Authentification Bearer token
✓ Idempotency-Key sur opérations critiques
✓ Retries + backoff côté client
✓ Tests Supertest pour chaque endpoint
✓ Documentation Swagger/OpenAPI exposée

Étape 1 — Comprendre ce qu’est une API REST avant d’écrire une ligne de code

Une API REST (Representational State Transfer) est une convention pour faire dialoguer deux logiciels via HTTP. Le client envoie une requête sur une URL, le serveur répond avec des données, généralement au format JSON. Quand vous payez chez un marchand à Dakar avec Wave et que la confirmation arrive sur le téléphone du commerçant, c’est une API REST qui a transporté l’événement entre l’app Wave et le terminal.

Avant d’intégrer une API, ouvrez sa documentation et repérez quatre éléments : l’URL de base (par exemple https://api.intouchgroup.net/v2), la méthode d’authentification (clé API, OAuth 2.0, JWT), la liste des endpoints (URLs accessibles), le format des réponses. Ces quatre éléments suffisent à esquisser un schéma sur papier avant de coder. Le livrable de cette étape est un fichier notes-api.md qui résume ces quatre points pour l’API ciblée.

Étape 2 — Installer un environnement de test avec curl, Postman ou Bruno

Avant d’intégrer l’API dans un projet, testez-la à la main. Installez Bruno (alternative open source à Postman, sans cloud obligatoire) ou utilisez curl en ligne de commande sur Linux, macOS ou Git Bash sous Windows. Bruno stocke les collections en fichiers texte versionnables dans Git, ce qui évite la dépendance au cloud Postman et convient mieux aux équipes ouest-africaines à connexion intermittente.

# Installation de Bruno (Ubuntu / Debian)
sudo apt install -y libfuse2
wget https://github.com/usebruno/bruno/releases/latest/download/bruno_linux_x86_64.AppImage
chmod +x bruno_linux_x86_64.AppImage
./bruno_linux_x86_64.AppImage

Le binaire ouvre une fenêtre où vous créez votre première collection. La preuve que ça tourne : vous pouvez cliquer « Send » sur la requête de démonstration et voir un JSON revenir en moins d’une seconde. Si l’AppImage ne se lance pas, vérifiez que libfuse2 est bien présent (erreur fréquente sur Ubuntu 24.04).

Étape 3 — Lire la documentation OpenAPI / Swagger

La majorité des API modernes publient une spécification OpenAPI 3.1 (anciennement Swagger). Ce fichier YAML ou JSON décrit chaque endpoint, ses paramètres, ses réponses possibles. Importez-le dans Bruno ou Swagger Editor pour générer automatiquement un client de test. Pour une API hypothétique de paiement mobile au Sénégal, la spec déclare par exemple POST /v2/payments qui accepte un montant en FCFA et retourne un identifiant de transaction.

Repérez systématiquement la section securitySchemes qui indique comment s’authentifier. Trois patterns dominent : apiKey dans un header, http bearer avec un JWT, ou oauth2 avec un flux de consentement. Notez la rate limit (souvent 60 requêtes/minute en sandbox). Sans cette lecture préalable, vous allez perdre une demi-journée à deviner les en-têtes corrects.

Étape 4 — Réaliser un premier appel GET authentifié

Une API publique simple pour s’entraîner sans risque : l’API REST de WordPress, accessible sur tout site WordPress. Pour itskillscenter.io, l’URL https://itskillscenter.io/wp-json/wp/v2/posts?per_page=3 renvoie les trois derniers articles. Pas de clé requise, taux d’appels modéré, parfait pour les premiers exercices.

curl -s "https://itskillscenter.io/wp-json/wp/v2/posts?per_page=3&_fields=id,title,link"   -H "Accept: application/json" | jq '.'
# Sortie attendue : tableau de 3 objets {id, title:{rendered}, link}

Si jq est absent, installez-le avec sudo apt install jq sur Debian ou brew install jq sur macOS. La réussite se mesure à un JSON valide affiché en couleurs dans le terminal. En cas d’erreur 401, vérifiez l’en-tête d’authentification ; en cas de 429, vous avez dépassé le rate limit, attendez 60 secondes avant de réessayer.

Étape 5 — Effectuer un POST pour créer une ressource

La méthode POST sert à créer. Cas pratique : poster une commande sur une API e-commerce de démo. Préparez un fichier JSON localement, envoyez-le avec le bon en-tête Content-Type, vérifiez le code HTTP de retour (201 Created en cas de succès, 400 si la validation échoue, 401 si vous n’êtes pas authentifié).

cat > commande.json <<'JSON'
{"client":"Aïssatou Diop","montant_fcfa":12500,"devise":"XOF","mode":"wave"}
JSON

curl -i -X POST https://api.exemple.sn/v1/commandes   -H "Authorization: Bearer $TOKEN"   -H "Content-Type: application/json"   --data @commande.json
# Attendu : HTTP/2 201 + JSON {id, status:"pending", created_at}

Si le serveur renvoie 422 Unprocessable Entity, c’est que les champs sont bien lus mais ne respectent pas le schéma : montant en centimes au lieu de FCFA entiers, devise non supportée, etc. Lisez le corps de la réponse pour comprendre quel champ a échoué.

Étape 6 — Gérer l’authentification OAuth 2.0 et les tokens

Pour les API qui touchent à de l’argent ou des données utilisateurs (Orange Money, Wave Business, Stripe), l’authentification se fait en OAuth 2.0 client_credentials. Le flux : votre serveur envoie son client_id et client_secret à l’endpoint /oauth/token, reçoit un access_token valide une heure, l’utilise dans l’en-tête Authorization: Bearer de chaque appel.

curl -X POST https://api.exemple.sn/oauth/token   -H "Content-Type: application/x-www-form-urlencoded"   -d "grant_type=client_credentials"   -d "client_id=$CLIENT_ID"   -d "client_secret=$CLIENT_SECRET"
# Sortie : {"access_token":"eyJhbGc...", "expires_in":3600, "token_type":"Bearer"}

Stockez ce token en mémoire applicative et rafraîchissez-le 5 minutes avant expiration pour éviter les coupures. Ne le commitez jamais dans Git. Pour un projet Node.js sérieux, utilisez le package simple-oauth2 qui gère le cycle de vie pour vous.

Étape 7 — Gérer les erreurs, le retry et l’idempotence

Une API en production échoue régulièrement : timeout réseau (fréquent sur les liens 4G ouest-africains), code 503 Service Unavailable, code 429 Too Many Requests. Votre code doit prévoir trois mécanismes : un timeout côté client (10 secondes maximum), une stratégie de retry exponentiel (1s, 2s, 4s, 8s, abandon), une clé d’idempotence pour éviter les doubles paiements.

L’idempotence est cruciale pour les API de paiement. Concrètement, vous générez un UUID v4 par tentative et vous le passez dans un en-tête Idempotency-Key. Si la requête est rejouée à cause d’un timeout, le serveur reconnaît la clé et ne facture qu’une seule fois. Stripe, Wave et la plupart des passerelles modernes implémentent ce pattern. Sans clé d’idempotence, un retry naïf peut débiter deux fois un client à Abidjan ou Dakar.

Étape 8 — Sécuriser, monitorer, documenter votre intégration

Une intégration API en production exige trois pratiques d’hygiène : variables d’environnement pour les secrets (jamais dans le code), journalisation structurée des appels (avec masquage des données sensibles), monitoring des taux d’erreur. Sur un projet Node.js, utilisez dotenv pour les secrets, pino pour les logs JSON, et un service comme Sentry ou Better Stack pour les alertes.

Côté documentation interne, maintenez un fichier INTEGRATION.md qui explique : à quoi sert l’API intégrée, qui détient les credentials, où trouver la sandbox, quels sont les codes d’erreur métier rencontrés, à quel SLA elle est soumise. Ce document évite que la connaissance disparaisse avec un développeur. Dans la continuité, consultez nos guides intégrer une API de paiement mobile au Sénégal et sécuriser une API REST en production.

Étape 9 — Versionner les contrats et anticiper les breaking changes

Une API évolue. Les fournisseurs publient régulièrement de nouvelles versions (v1, v2, v3) qui cassent la compatibilité. Pour ne pas découvrir un dimanche matin que votre intégration de paiement renvoie 404 sur tous les appels, suivez trois pratiques : abonnez-vous à la mailing list ou au flux RSS de changelog du fournisseur, exécutez un test smoke quotidien automatique sur un compte sandbox, gardez le numéro de version explicite dans l’URL (préférez /v2/payments à un endpoint sans version).

Quand le fournisseur annonce une dépréciation (par exemple, fin de la v1 dans six mois), créez immédiatement une issue dans votre tracker, planifiez la migration, et testez la nouvelle version dans un environnement isolé. Conservez l’ancien code derrière un flag pour pouvoir basculer en deux minutes si la nouvelle version pose problème en production.

Étape 10 — Auditer la consommation et optimiser les coûts

Beaucoup d’API publiques sont gratuites jusqu’à un certain volume puis facturées. Une intégration mal conçue peut générer des appels inutiles : récupération de la liste complète à chaque vue au lieu d’un cache, polling toutes les secondes au lieu d’utiliser les webhooks, requêtes non paginées qui rapatrient 10 000 enregistrements pour en afficher 20.

Mettez en place trois optimisations : un cache HTTP côté client (en-tête Cache-Control respecté, ou Redis pour 60 secondes sur les ressources peu changeantes), des webhooks à la place du polling quand le fournisseur les propose, une pagination explicite avec per_page raisonnable (20 à 100). Mesurez le nombre d’appels par jour avec votre tableau de bord fournisseur. Une intégration bien conçue divise typiquement la facture par cinq par rapport à une intégration naïve.

Partager