Le frontend qui consomme l'API
Frontend fetch, trois états de vue et rendu depuis le modèle.
Objectifs
À la fin de cette leçon, vous saurez :
- brancher votre todo-list sur l'API au lieu de localStorage ;
- afficher les données serveur et gérer les états (chargement, erreur) ;
- organiser le rendu autour du modèle reçu.
🔗 Pour vous rafraîchir la mémoire : fs et path côté Node · en-têtes HTTP et JSON
Remplacer la source de vérité
Au chapitre 16, taches vivait en mémoire + localStorage. Désormais il vit en base, et le frontend ne fait que refléter ce que l'API renvoie :
async function chargerTaches() {
etat = "chargement";
afficher();
try {
const reponse = await fetch("/tasks");
if (!reponse.ok) throw new Error("HTTP " + reponse.status);
taches = await reponse.json();
etat = "pret";
} catch (erreur) {
etat = "erreur";
}
afficher();
}
Les trois états d'une vue
Une interface connectée a toujours un état à afficher :
chargement → « Chargement... » (spinner)
erreur → message + bouton Réessayer
prêt → les données
Oublier ces états est LA faute des interfaces débutantes : page blanche pendant le réseau, silence en cas de panne. Le pattern ci-dessus — une variable etat + un seul afficher() qui la traite — suffit largement.
Les actions, symétriques du backend
async function ajouterTache(texte) {
const reponse = await fetch("/tasks", {
method: "POST",
headers: { "Content-Type": "application/json" },
body: JSON.stringify({ titre: texte }),
});
if (reponse.status === 201) {
await chargerTaches(); // recharger : simple et sûr
} else {
afficherErreur("Ajout refusé");
}
}
async function basculerFait(id, fait) {
await fetch("/tasks/" + id, {
method: "PATCH",
headers: { "Content-Type": "application/json" },
body: JSON.stringify({ fait: fait }),
});
await chargerTaches();
}
Choix assumé : après chaque action on recharge tout plutôt que de patcher le DOM. Sur cette taille d'application, c'est plus simple et toujours correct ; les optimisations viendront avec les frameworks.
Le fichier unique servi par Node
Votre serveur peut servir lui-même l'interface :
import { readFile } from "node:fs/promises";
// dans le routeur :
if (requete.method === "GET" && chemin === "/") {
const html = await readFile("public/index.html", "utf8");
reponse.writeHead(200, { "Content-Type": "text/html; charset=utf-8" });
return reponse.end(html);
}
Une seule origine : pas de CORS à gérer (chapitre sécurité), un seul processus à lancer.
Exercice
- Créez public/index.html + public/app.js ; branchez chargerTaches/ajouter/basculer/supprimer.
- Implémentez les trois états visuels.
- Arrêtez PostgreSQL puis cliquez : observez l'état erreur, relancez, réessayez.
- Pourquoi recharger toute la liste après chaque action reste-t-il correct ici ? Quand devient-ce insuffisant ?
Résumé
- fetch remplace localStorage ; l'API devient la source de vérité.
- Trois états systématiques : chargement / erreur / prêt.
- Rendre depuis les données, recharger après mutation : simplicité délibérée.
Correction disponibleCherchez d’abord par vous-même.Voir la correction
Correction
Réponses détaillées
Question 4. Correct tant que le volume est petit et l'équipe petite. Insuffisant quand : listes paginées (recharger tout perd le scroll/filtres), concurrence multi-utilisateurs forte (besoin de mises à jour ciblées ou temps réel). C'est alors qu'arrivent les requêtes optimistes et les frameworks réactifs — mais leur logique restera exactement celle-ci.