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.
Plan du chapitre
- 1) Pourquoi éviter le fetch partout ?
- 2) Créer un dossier services/
- 3) Un “client” fetch réutilisable
- 4) Services par ressource : users.service.js
- 5) Utiliser les services dans un hook (pattern recommandé)
- 6) Gestion d’erreurs : afficher, logger, différencier
- 7) Variables d’environnement : URL de base
- 8) Résumé (à retenir)
- 9) Exercice pratique
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 usershooks/: 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.jsavec :getPosts(),getPost(id). - Créer
src/hooks/usePosts.jsqui gèreposts,loading,error. - Créer une page
PostsPagequi affiche la liste. - Bonus : créer
PostPagequi 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.