Tutoriel React — Chapitre 22

Appels API “propres” : dossier services/, gestion des erreurs et préparation backend

Chapitre 22 — Appels API “propres” : dossier services/, gestion des erreurs et préparation backend

Faire un fetch directement dans un composant fonctionne, mais dans un vrai projet, on veut un code maintenable : une logique réseau centralisée, des erreurs traitées, et une base prête à accueillir un vrai backend. Dans ce chapitre, vous allez apprendre une méthode simple et “pro” pour organiser vos appels API.

Objectif : comprendre (pas apprendre par cœur) Niveau : débutant absolu Pratique : mini-exercice à la fin

Plan du chapitre

1) Pourquoi éviter le fetch partout ?

Si chaque composant fait son propre fetch :

  • vous dupliquez la logique (headers, parsing JSON, erreurs…)
  • vous avez des incohérences (un composant gère les erreurs, l’autre non)
  • changer l’URL de base devient une corvée
  • tester / déboguer devient plus difficile

Solution : centraliser les appels réseau dans services/ (et parfois dans des hooks).

2) Créer un dossier services/

Une structure très utilisée :

src/
├─ services/
│  ├─ api-client.js
│  ├─ users.service.js
│  └─ posts.service.js
└─ hooks/
   └─ useUsers.js
  • api-client.js : fonctions génériques (GET/POST/DELETE…)
  • users.service.js : endpoints liés aux users
  • hooks/ : logique UI réutilisable (loading/error/data)

3) Un “client” fetch réutilisable

On va créer une fonction request qui :

  • appelle fetch
  • gère res.ok
  • parse le JSON
  • renvoie des erreurs cohérentes

Fichier : src/services/api-client.js

const BASE_URL = import.meta.env.VITE_API_URL || "";

export async function request(path, options = {}) {
  const url = BASE_URL + path;

  const res = await fetch(url, {
    headers: {
      "Content-Type": "application/json",
      ...(options.headers || {})
    },
    ...options
  });

  // Essayer de lire le JSON (pas toujours présent)
  let data = null;
  const contentType = res.headers.get("content-type") || "";

  if (contentType.includes("application/json")) {
    data = await res.json();
  } else {
    // fallback texte
    const text = await res.text();
    data = text ? { message: text } : null;
  }

  if (!res.ok) {
    const message =
      (data && data.message) || `Erreur HTTP ${res.status} (${res.statusText})`;
    const error = new Error(message);
    error.status = res.status;
    error.data = data;
    throw error;
  }

  return data;
}

export const api = {
  get: (path) => request(path),
  post: (path, body) =>
    request(path, {
      method: "POST",
      body: JSON.stringify(body)
    }),
  put: (path, body) =>
    request(path, {
      method: "PUT",
      body: JSON.stringify(body)
    }),
  del: (path) =>
    request(path, {
      method: "DELETE"
    })
};

Ici, on utilise VITE_API_URL (Vite) pour gérer l’URL de base. Si vous n’avez pas d’API, vous pouvez laisser vide pour des URLs absolues.

4) Services par ressource : users.service.js

Le service “users” contient des fonctions claires : getUsers(), getUser(id), createUser(user), etc.

Fichier : src/services/users.service.js

import { api } from "./api-client.js";

export function getUsers() {
  return api.get("/users");
}

export function getUser(id) {
  return api.get(`/users/${id}`);
}

export function createUser(payload) {
  return api.post("/users", payload);
}

export function deleteUser(id) {
  return api.del(`/users/${id}`);
}

Règle : un service ne s’occupe pas d’UI. Il renvoie juste des promesses (data ou erreur).

5) Utiliser les services dans un hook (pattern recommandé)

On combine le service et un hook pour gérer : loading, error, data. Cela évite de réécrire la même logique dans chaque page.

Fichier : src/hooks/useUsers.js

import { useEffect, useState } from "react";
import { getUsers } from "../services/users.service.js";

export function useUsers() {
  const [users, setUsers] = useState([]);
  const [loading, setLoading] = useState(false);
  const [error, setError] = useState(null);

  useEffect(() => {
    let alive = true;
    setLoading(true);
    setError(null);

    getUsers()
      .then((data) => {
        if (!alive) return;
        setUsers(Array.isArray(data) ? data : []);
      })
      .catch((err) => {
        if (!alive) return;
        setError(err.message || "Erreur inconnue");
      })
      .finally(() => {
        if (!alive) return;
        setLoading(false);
      });

    return () => {
      alive = false;
    };
  }, []);

  return { users, loading, error };
}

Ensuite, la page devient très simple : elle affiche selon les états.

import { useUsers } from "../hooks/useUsers.js";

export default function UsersPage() {
  const { users, loading, error } = useUsers();

  if (loading) return <p>Chargement...</p>
  if (error) return <p>Erreur : {error}</p>

  return (
    <ul>
      {users.map((u) => (
        <li key={u.id}>{u.name}</li>
      ))}
    </ul>
  );
}

6) Gestion d’erreurs : afficher, logger, différencier

Dans un projet, vous voulez :

  • Afficher une erreur compréhensible à l’utilisateur
  • Garder une info technique pour vous (console / monitoring)
  • Différencier certains cas : 401 (auth), 404, 500, etc.
catch((err) => {
  console.error("Erreur API:", err); // info dev

  if (err.status === 401) {
    setError("Vous devez être connecté.");
  } else if (err.status === 404) {
    setError("Ressource introuvable.");
  } else {
    setError(err.message || "Erreur serveur.");
  }
});

Même si vous affichez un message simple, gardez la trace technique côté dev.

7) Variables d’environnement : URL de base

Avec Vite, vous pouvez créer un fichier .env à la racine du projet :

VITE_API_URL=https://mon-api.exemple.com

Ensuite, votre api-client utilise import.meta.env.VITE_API_URL. Ainsi, vous changez d’API (local / prod) sans modifier le code.

8) Résumé (à retenir)

  • Évitez le fetch dans tous les composants : centralisez.
  • services/ : fonctions d’API par ressource.
  • api-client : gestion commune (headers, JSON, erreurs).
  • Les hooks (useUsers…) rendent les pages plus simples.
  • Utilisez des variables d’environnement pour l’URL de base.

9) Exercice pratique

Objectif : structurer une ressource “posts”.

À faire

  • Créer src/services/posts.service.js avec : getPosts(), getPost(id).
  • Créer src/hooks/usePosts.js qui gère posts, loading, error.
  • Créer une page PostsPage qui affiche la liste.
  • Bonus : créer PostPage qui affiche un post selon l’ID (avec React Router).

Prochaine étape : Chapitre 23 — Authentification (avec services/ et routes protégées) (niveau débutant) : login/logout, routes protégées, et stockage du token.