Aller au contenu principal

Frontend React consommant l’API FastAPI

remarque

Ce chapitre est un bonus pratique à lire après le cœur backend. Il montre comment connecter un petit frontend React à l’API FastAPI, sans faire du frontend un prérequis pour comprendre le wiki.

Introduction

Après les chapitres backend, l’API dispose déjà d’authentification, de routes protégées et d’un vrai flux utilisateur. Ce chapitre montre comment brancher un client React minimal mais réel sur ce backend : login, lecture des tâches, création de tâche et demande de reset.

Ce que ce chapitre va accomplir

À la fin de ce chapitre, tu auras :

  • une base React + Vite + TypeScript,
  • un client API centralisé,
  • un AuthContext pour gérer le token,
  • un ProtectedRoute,
  • une page de login,
  • une page de demande de reset password,
  • un dashboard qui lit les tâches de l’utilisateur,
  • un formulaire de création de tâche,
  • la configuration CORS côté backend,
  • une compréhension claire de :
    • localStorage,
    • header Authorization,
    • react-router-dom,
    • @tanstack/react-query,
    • intégration frontend/backend locale.

Avant de commencer

Ce chapitre reste un bonus, mais il a quelques prérequis précis :

  • les routes d’authentification doivent déjà fonctionner,
  • /user/me, /task/mine et /task/ doivent répondre côté backend,
  • CORS doit pouvoir être activé,
  • Celery n’est pas un prérequis pour suivre le frontend.

Autrement dit, lis ce chapitre après le cœur API, pas forcément après chaque détail d’infrastructure.

Sources et choix pédagogiques

Ce chapitre s’appuie principalement sur :

  • 31-React JS/155-context.zip
  • 31-React JS/158-src.zip
  • 32-Frontend/166-app.zip

Les sources montrent trois niveaux d’évolution :

  • un contexte React simple,
  • une consommation API avec client typé,
  • un frontend plus complet avec auth et dashboard.

Pour le wiki, on garde une version volontairement plus petite :

  • Vite + React + TypeScript,
  • react-router-dom,
  • @tanstack/react-query,
  • axios,
  • AuthContext,
  • dashboard tâches,
  • login et reset password.

Architecture visée après ce chapitre

On ajoute un dossier frontend séparé du backend :

fastapi-base-api/
├── main.py
├── config.py
├── api/
├── core/
├── database/
├── services/
├── worker/
├── templates/
└── frontend/
├── package.json
├── vite.config.ts
├── .env
├── index.html
└── src/
├── main.tsx
├── App.tsx
├── lib/
│ └── api.ts
├── context/
│ └── AuthContext.tsx
├── components/
│ ├── ProtectedRoute.tsx
│ ├── TaskCard.tsx
│ └── CreateTaskForm.tsx
└── pages/
├── LoginPage.tsx
├── ForgotPasswordPage.tsx
└── DashboardPage.tsx

Repères utiles avant le code

Avant de commencer, retiens ces idées simples :

1. Le frontend et le backend sont deux projets séparés

C’est important mentalement.

Le frontend :

  • appelle l’API,
  • ne remplace pas l’API,
  • ne doit pas embarquer la logique métier sensible.

2. Le login FastAPI ici attend du x-www-form-urlencoded

C’est très important.

Notre route /user/token utilise OAuth2PasswordRequestForm. Donc côté React, tu ne peux pas envoyer n’importe quel JSON brut. Il faut envoyer un payload compatible formulaire.

3. Le token doit être centralisé

Si tu gères le token dans dix composants différents, ton frontend devient vite fragile. C’est précisément le rôle de AuthContext.

4. Le dashboard doit lire l’API, pas simuler des données locales

Le but n’est pas de faire une maquette fake. Le but est de montrer comment l’UI consomme vraiment :

  • /user/token
  • /user/me
  • /task/mine
  • /task/
  • /user/password-reset/request

5. CORS est un vrai prérequis

Si ton backend refuse les appels depuis http://localhost:5173, rien ne marchera côté navigateur.

Étape 1 — créer le frontend React

Place-toi à la racine du projet backend, puis lance :

npm create vite@latest frontend -- --template react-ts
cd frontend
npm install react-router-dom @tanstack/react-query axios

Pourquoi cette stack ?

Vite

Rapide, simple, parfait pour un tutoriel pédagogique.

react-router-dom

Pour avoir une vraie navigation frontend.

@tanstack/react-query

Pour gérer les appels API, le cache et l’invalidation proprement.

axios

Pour centraliser le client HTTP simplement.

Étape 2 — ajouter CORS côté backend

Avant même d’écrire React, il faut permettre au navigateur d’appeler l’API.

Fais évoluer main.py comme ceci :

from contextlib import asynccontextmanager

from fastapi import FastAPI
from fastapi.middleware.cors import CORSMiddleware
from scalar_fastapi import get_scalar_api_reference

from api.router import router
from core.exceptions import add_exception_handlers
from core.middleware import request_metrics_middleware
from database.session import create_db_tables


@asynccontextmanager
async def lifespan(app: FastAPI):
await create_db_tables()
yield


app = FastAPI(
title="Task Organizer API",
description="Une API FastAPI consommée par un frontend React",
lifespan=lifespan,
)

add_exception_handlers(app)
app.middleware("http")(request_metrics_middleware)

app.add_middleware(
CORSMiddleware,
allow_origins=[
"http://localhost:5173",
],
allow_credentials=True,
allow_methods=["*"],
allow_headers=["*"],
)

app.include_router(router)


@app.get("/scalar", include_in_schema=False)
def get_scalar_docs():
return get_scalar_api_reference(
openapi_url=app.openapi_url,
title="Scalar API",
)

Pourquoi cette étape est indispensable

Sans cela, le navigateur bloquera les requêtes cross-origin.

C’est un point classique quand on passe de :

  • curl
  • ou tests backend

à :

  • un vrai frontend web.

Étape 3 — préparer l’environnement frontend

Crée frontend/.env :

VITE_API_URL=http://localhost:8000

Pourquoi passer par VITE_API_URL

Parce que ça évite de hardcoder partout http://localhost:8000. Et plus tard, tu pourras remplacer plus facilement l’URL pour :

  • un staging,
  • un déploiement,
  • un reverse proxy.

Étape 4 — créer src/lib/api.ts

Ici, on choisit volontairement un client manuel simple.

remarque

Les sources montrent aussi un client généré à partir d’OpenAPI. C’est très utile plus tard, mais pour apprendre, une version manuelle reste plus lisible.

Crée frontend/src/lib/api.ts :

import axios from "axios";

export type StatusTask = "to_do" | "in_progress" | "completed";

export interface UserRead {
id: number;
name: string;
email: string;
email_verified: boolean;
}

export interface TaskRead {
id: number;
title: string;
description: string;
status: StatusTask;
owner_id: number;
}

export interface TaskCreate {
title: string;
description: string;
}

export interface TokenResponse {
access_token: string;
token_type: string;
}

const api = axios.create({
baseURL: import.meta.env.VITE_API_URL,
});

export function setAuthToken(token: string | null) {
if (token) {
api.defaults.headers.common.Authorization = `Bearer ${token}`;
} else {
delete api.defaults.headers.common.Authorization;
}
}

export async function loginUser(email: string, password: string) {
const form = new URLSearchParams();
form.append("username", email);
form.append("password", password);

const { data } = await api.post<TokenResponse>("/user/token", form, {
headers: {
"Content-Type": "application/x-www-form-urlencoded",
},
});

return data;
}

export async function getMe() {
const { data } = await api.get<UserRead>("/user/me");
return data;
}

export async function getMyTasks() {
const { data } = await api.get<TaskRead[]>("/task/mine");
return data;
}

export async function createTask(payload: TaskCreate) {
const { data } = await api.post<TaskRead>("/task/", payload);
return data;
}

export async function requestPasswordReset(email: string) {
const { data } = await api.post("/user/password-reset/request", {
email,
});
return data;
}

Ce qu’il faut remarquer

setAuthToken()

Cette fonction centralise l’injection du header Authorization.

URLSearchParams

C’est ici qu’on respecte le format attendu par /user/token.

Le frontend connaît les types utiles

On définit seulement les types nécessaires au frontend. Cela suffit largement pour démarrer.

Étape 5 — créer src/context/AuthContext.tsx

Crée frontend/src/context/AuthContext.tsx :

import {
createContext,
useContext,
useEffect,
useMemo,
useState,
type ReactNode,
} from "react";

import { getMe, loginUser, setAuthToken, type UserRead } from "../lib/api";

interface AuthContextType {
token: string | null;
user: UserRead | null;
isReady: boolean;
login: (email: string, password: string) => Promise<void>;
logout: () => void;
}

const AuthContext = createContext<AuthContextType | undefined>(undefined);

export function AuthProvider({ children }: { children: ReactNode }) {
const [token, setToken] = useState<string | null>(null);
const [user, setUser] = useState<UserRead | null>(null);
const [isReady, setIsReady] = useState(false);

useEffect(() => {
async function bootstrap() {
const savedToken = localStorage.getItem("token");

if (!savedToken) {
setIsReady(true);
return;
}

try {
setAuthToken(savedToken);
const me = await getMe();
setToken(savedToken);
setUser(me);
} catch {
localStorage.removeItem("token");
setAuthToken(null);
setToken(null);
setUser(null);
} finally {
setIsReady(true);
}
}

bootstrap();
}, []);

async function login(email: string, password: string) {
const data = await loginUser(email, password);

localStorage.setItem("token", data.access_token);
setAuthToken(data.access_token);
setToken(data.access_token);

const me = await getMe();
setUser(me);
}

function logout() {
localStorage.removeItem("token");
setAuthToken(null);
setToken(null);
setUser(null);
}

const value = useMemo(
() => ({ token, user, isReady, login, logout }),
[token, user, isReady],
);

return <AuthContext.Provider value={value}>{children}</AuthContext.Provider>;
}

export function useAuth() {
const context = useContext(AuthContext);
if (!context) {
throw new Error("useAuth must be used inside AuthProvider");
}
return context;
}

Pourquoi cette structure est saine

  • le token est centralisé,
  • le user courant est centralisé,
  • le bootstrap recharge la session après refresh navigateur,
  • l’UI ne dépend pas de localStorage partout.

Étape 6 — créer src/components/ProtectedRoute.tsx

Crée frontend/src/components/ProtectedRoute.tsx :

import { Navigate } from "react-router-dom";

import { useAuth } from "../context/AuthContext";

export function ProtectedRoute({ children }: { children: JSX.Element }) {
const { token, isReady } = useAuth();

if (!isReady) {
return <p>Chargement de la session...</p>;
}

if (!token) {
return <Navigate to="/login" replace />;
}

return children;
}

Pourquoi c’est utile

Parce qu’on veut éviter que :

  • la page dashboard s’ouvre sans token,
  • chaque page protège la session à sa manière.

Étape 7 — créer src/components/TaskCard.tsx

Crée frontend/src/components/TaskCard.tsx :

import type { TaskRead } from "../lib/api";

export function TaskCard({ task }: { task: TaskRead }) {
return (
<article
style={{
border: "1px solid #e5e7eb",
borderRadius: 16,
padding: 16,
background: "white",
}}
>
<p style={{ color: "#6b7280", marginBottom: 8 }}>Task #{task.id}</p>
<h3 style={{ marginBottom: 8 }}>{task.title}</h3>
<p style={{ marginBottom: 12 }}>{task.description}</p>
<strong>Status: {task.status}</strong>
</article>
);
}

Étape 8 — créer src/components/CreateTaskForm.tsx

Crée frontend/src/components/CreateTaskForm.tsx :

import { useState } from "react";
import { useMutation, useQueryClient } from "@tanstack/react-query";

import { createTask } from "../lib/api";

export function CreateTaskForm() {
const queryClient = useQueryClient();
const [title, setTitle] = useState("");
const [description, setDescription] = useState("");

const mutation = useMutation({
mutationFn: createTask,
onSuccess: () => {
setTitle("");
setDescription("");
queryClient.invalidateQueries({ queryKey: ["my-tasks"] });
},
});

async function handleSubmit(event: React.FormEvent<HTMLFormElement>) {
event.preventDefault();

if (!title.trim() || !description.trim()) {
return;
}

mutation.mutate({ title, description });
}

return (
<form
onSubmit={handleSubmit}
style={{
display: "grid",
gap: 12,
padding: 16,
border: "1px solid #e5e7eb",
borderRadius: 16,
background: "white",
}}
>
<h2>Créer une tâche</h2>

<input
value={title}
onChange={(event) => setTitle(event.target.value)}
placeholder="Titre"
/>

<textarea
value={description}
onChange={(event) => setDescription(event.target.value)}
placeholder="Description"
rows={4}
/>

<button type="submit" disabled={mutation.isPending}>
{mutation.isPending ? "Création..." : "Créer"}
</button>

{mutation.isError && <p>Échec de création de la tâche.</p>}
</form>
);
}

Ce que montre ce composant

C’est l’équivalent pédagogique de ce qu’on voit dans les sources frontend avec :

  • formulaire,
  • mutation,
  • invalidation du cache,
  • UI branchée sur une vraie route backend.

Étape 9 — créer src/pages/LoginPage.tsx

Crée frontend/src/pages/LoginPage.tsx :

import { useState } from "react";
import { Link, useNavigate } from "react-router-dom";

import { useAuth } from "../context/AuthContext";

export function LoginPage() {
const { login } = useAuth();
const navigate = useNavigate();

const [email, setEmail] = useState("");
const [password, setPassword] = useState("");
const [error, setError] = useState<string | null>(null);
const [loading, setLoading] = useState(false);

async function handleSubmit(event: React.FormEvent<HTMLFormElement>) {
event.preventDefault();
setError(null);
setLoading(true);

try {
await login(email, password);
navigate("/dashboard");
} catch {
setError("Connexion impossible. Vérifie les identifiants ou la validation de l’email.");
} finally {
setLoading(false);
}
}

return (
<main style={{ maxWidth: 420, margin: "48px auto", display: "grid", gap: 16 }}>
<h1>Connexion</h1>

<form onSubmit={handleSubmit} style={{ display: "grid", gap: 12 }}>
<input
type="email"
placeholder="email@example.com"
value={email}
onChange={(event) => setEmail(event.target.value)}
required
/>

<input
type="password"
placeholder="Mot de passe"
value={password}
onChange={(event) => setPassword(event.target.value)}
required
/>

<button type="submit" disabled={loading}>
{loading ? "Connexion..." : "Se connecter"}
</button>
</form>

{error && <p>{error}</p>}

<Link to="/forgot-password">Mot de passe oublié ?</Link>
</main>
);
}

Lien avec les sources

Cette page reprend l’esprit de :

  • app/routes/seller/login.tsx
  • app/components/login-form.tsx
  • app/contexts/AuthContext.tsx

mais dans une version plus simple et plus lisible pour le wiki.

Étape 10 — créer src/pages/ForgotPasswordPage.tsx

Crée frontend/src/pages/ForgotPasswordPage.tsx :

import { useState } from "react";

import { requestPasswordReset } from "../lib/api";

export function ForgotPasswordPage() {
const [email, setEmail] = useState("");
const [message, setMessage] = useState<string | null>(null);
const [loading, setLoading] = useState(false);

async function handleSubmit(event: React.FormEvent<HTMLFormElement>) {
event.preventDefault();
setLoading(true);
setMessage(null);

try {
await requestPasswordReset(email);
setMessage("Si le compte existe, un email de reset a été envoyé.");
} finally {
setLoading(false);
}
}

return (
<main style={{ maxWidth: 420, margin: "48px auto", display: "grid", gap: 16 }}>
<h1>Mot de passe oublié</h1>

<form onSubmit={handleSubmit} style={{ display: "grid", gap: 12 }}>
<input
type="email"
placeholder="email@example.com"
value={email}
onChange={(event) => setEmail(event.target.value)}
required
/>

<button type="submit" disabled={loading}>
{loading ? "Envoi..." : "Envoyer le lien"}
</button>
</form>

{message && <p>{message}</p>}
</main>
);
}

Ce que montre cette page

Elle illustre parfaitement un flow utilisateur réel :

  • formulaire simple,
  • appel POST vers l’API,
  • réponse volontairement générique,
  • UX cohérente avec le chapitre notifications.

Étape 11 — créer src/pages/DashboardPage.tsx

Crée frontend/src/pages/DashboardPage.tsx :

import { useQuery } from "@tanstack/react-query";

import { CreateTaskForm } from "../components/CreateTaskForm";
import { TaskCard } from "../components/TaskCard";
import { useAuth } from "../context/AuthContext";
import { getMyTasks } from "../lib/api";

export function DashboardPage() {
const { user, logout } = useAuth();

const { data, isLoading, isError } = useQuery({
queryKey: ["my-tasks"],
queryFn: getMyTasks,
});

return (
<main style={{ maxWidth: 980, margin: "32px auto", display: "grid", gap: 24 }}>
<header
style={{
display: "flex",
justifyContent: "space-between",
alignItems: "center",
gap: 16,
}}
>
<div>
<p style={{ color: "#6b7280" }}>Connecté en tant que</p>
<h1>{user?.name}</h1>
<p>{user?.email}</p>
</div>

<button onClick={logout}>Se déconnecter</button>
</header>

<CreateTaskForm />

<section style={{ display: "grid", gap: 16 }}>
<h2>Mes tâches</h2>

{isLoading && <p>Chargement des tâches...</p>}
{isError && <p>Impossible de charger les tâches.</p>}

{!isLoading && !isError && data?.length === 0 && (
<p>Aucune tâche pour le moment.</p>
)}

<div style={{ display: "grid", gap: 16 }}>
{data?.map((task) => (
<TaskCard key={task.id} task={task} />
))}
</div>
</section>
</main>
);
}

Pourquoi ce dashboard est intéressant

Il relie plusieurs briques du wiki :

  • auth,
  • protection frontend,
  • lecture des tâches possédées,
  • création de tâche,
  • invalidation React Query,
  • session frontend durable.

Étape 12 — créer src/App.tsx

Crée frontend/src/App.tsx :

import { Navigate, Route, Routes } from "react-router-dom";

import { ProtectedRoute } from "./components/ProtectedRoute";
import { DashboardPage } from "./pages/DashboardPage";
import { ForgotPasswordPage } from "./pages/ForgotPasswordPage";
import { LoginPage } from "./pages/LoginPage";

function App() {
return (
<Routes>
<Route path="/" element={<Navigate to="/login" replace />} />
<Route path="/login" element={<LoginPage />} />
<Route path="/forgot-password" element={<ForgotPasswordPage />} />
<Route
path="/dashboard"
element={
<ProtectedRoute>
<DashboardPage />
</ProtectedRoute>
}
/>
</Routes>
);
}

export default App;

Étape 13 — créer src/main.tsx

Crée frontend/src/main.tsx :

import React from "react";
import ReactDOM from "react-dom/client";
import { BrowserRouter } from "react-router-dom";
import { QueryClient, QueryClientProvider } from "@tanstack/react-query";

import App from "./App";
import { AuthProvider } from "./context/AuthContext";

const queryClient = new QueryClient();

ReactDOM.createRoot(document.getElementById("root")!).render(
<React.StrictMode>
<QueryClientProvider client={queryClient}>
<BrowserRouter>
<AuthProvider>
<App />
</AuthProvider>
</BrowserRouter>
</QueryClientProvider>
</React.StrictMode>,
);

Étape 14 — lancer le frontend

Depuis frontend/ :

npm run dev

Le frontend devrait être disponible sur :

http://localhost:5173

Et le backend sur :

http://localhost:8000

Vérification guidée

1. Vérifier le backend

Avant le frontend, assure-toi que l’API répond toujours :

curl -i http://localhost:8000/docs

2. Vérifier le login backend à la main

Tu peux tester le token avant même d’ouvrir React :

curl -i \
-X POST http://localhost:8000/user/token \
-H "Content-Type: application/x-www-form-urlencoded" \
-d 'username=enes@test.local&password=StrongPass123!'

3. Ouvrir le frontend

Va sur :

http://localhost:5173

4. Tenter une connexion

Si l’email n’est pas vérifié, tu dois voir un échec de connexion. Si le compte est vérifié, tu dois être redirigé vers /dashboard.

5. Vérifier le dashboard

Une fois connecté :

  • le frontend doit appeler /user/me,
  • puis /task/mine,
  • puis afficher les tâches.

6. Créer une tâche

Depuis le formulaire du dashboard :

  • une requête POST doit partir vers /task/,
  • la liste doit ensuite se rafraîchir.

7. Tester le mot de passe oublié

Depuis /forgot-password :

  • envoie un email,
  • observe le message de confirmation générique.

Ce que ce chapitre ne fait pas encore

Ce chapitre ne couvre pas encore :

  • un design system complet,
  • un back-office React riche,
  • SSR,
  • Remix/Next.js,
  • génération automatique complète du client API,
  • forms ultra-avancées,
  • gestion de refresh token.

Et c’est volontaire.

Le but ici est de montrer comment :

  • connecter React à ton API,
  • centraliser auth + token,
  • lire et écrire des données métier,
  • brancher un vrai dashboard simple.

Lien plus direct avec les sources frontend

Ce qu’on a gardé

  • l’idée de UserContext / AuthContext,
  • l’idée d’un client API centralisé,
  • l’idée de pages login / forgot-password / dashboard,
  • l’idée de formulaires connectés à l’API,
  • l’idée d’une app réellement consommatrice du backend.

Ce qu’on a simplifié

  • on n’a pas gardé toute l’architecture UI avancée,
  • on n’a pas gardé tout le design system,
  • on n’a pas gardé la couche app-framework complète telle quelle.

Pourquoi ? Parce que pour le wiki, l’objectif est d’abord de faire comprendre le branchement frontend/backend.

Bonus — quand passer à un client généré ?

La source 31-React JS/158-src.zip montre un client.ts généré. C’est très bien quand :

  • l’API grossit,
  • les schémas sont nombreux,
  • tu veux réduire le code HTTP manuel.

Mais pédagogiquement, il vaut mieux d’abord comprendre :

  • le login,
  • le header Authorization,
  • la récupération de /user/me,
  • les appels protégés.

Ensuite seulement, un client généré devient vraiment rentable.

Erreurs fréquentes

Erreur 1 — blocage CORS dans le navigateur

Cause fréquente :

  • oubli de CORSMiddleware.

Correction :

  • ajoute bien http://localhost:5173 dans allow_origins.

Erreur 2 — login en 422 ou 400

Cause fréquente :

  • payload envoyé en JSON au lieu de x-www-form-urlencoded.

Correction :

  • utilise URLSearchParams et le bon header Content-Type.

Erreur 3 — dashboard vide alors que des tâches existent

Cause fréquente :

  • token non injecté,
  • mauvais utilisateur,
  • endpoint incorrect.

Correction :

  • vérifie setAuthToken() et l’appel /task/mine.

Erreur 4 — après refresh, l’utilisateur est déconnecté alors que le token existe

Cause fréquente :

  • oubli du bootstrap dans AuthContext.

Correction :

  • recharge le token depuis localStorage puis appelle /user/me.

Erreur 5 — la tâche est créée mais l’UI ne se met pas à jour

Cause fréquente :

  • oubli de invalidateQueries({ queryKey: ["my-tasks"] }).

Correction :

  • invalide correctement le cache React Query.

Erreur 6 — VITE_API_URL n’est pas prise en compte

Cause fréquente :

  • variable mal préfixée,
  • serveur Vite non relancé.

Correction :

  • garde le préfixe VITE_ et relance npm run dev.

Bonnes pratiques à retenir

1. Garde le frontend séparé du backend

Un dossier frontend/ dédié est beaucoup plus propre.

2. Centralise le client API

Évite les appels fetch dispersés partout.

3. Centralise la session dans un contexte

Évite de manipuler directement localStorage dans tous les composants.

4. Utilise React Query pour les données serveur

C’est beaucoup plus propre que de multiplier les useEffect manuels.

5. Commence simple, puis raffine l’UI ensuite

Le plus important ici est le flux technique, pas le maquillage final.

Checklist de validation

À la fin, tu dois pouvoir cocher tout ceci :

  • j’ai créé le projet React avec Vite
  • j’ai ajouté react-router-dom, @tanstack/react-query et axios
  • j’ai activé CORS côté FastAPI
  • j’ai créé src/lib/api.ts
  • j’ai créé AuthContext
  • j’ai créé ProtectedRoute
  • je sais appeler /user/token correctement
  • je sais stocker et réinjecter le token
  • je sais lire /user/me
  • je sais lire /task/mine
  • je sais créer une tâche depuis le frontend
  • je sais déclencher une demande de reset password depuis React

Résumé final

Dans ce chapitre bonus, tu as appris à faire vivre ton API FastAPI dans une vraie interface cliente React.

Le point le plus important à retenir est simple :

  • le backend garde la logique métier,
  • le frontend gère l’expérience utilisateur,
  • et les deux se parlent proprement à travers :
    • un client API,
    • un contexte d’auth,
    • un routage clair,
    • des appels réseau bien structurés.

Autrement dit : ce chapitre te fait passer de "j’ai une API" à "j’ai une API qui peut déjà alimenter un produit web".

Pour aller plus loin