Une application full-stack associe deux programmes :
- le client : une application React (construite avec Vite) qui s’exécute dans le navigateur ;
- le serveur : une API Express qui s’exécute sur Node.js et accède à la base de données.
Ils ne partagent ni mémoire ni variables : ils communiquent uniquement par des requêtes HTTP échangeant du JSON. L’ensemble des routes, formats et codes d’erreur forme le contrat d’API ; le documenter (README, OpenAPI) ou le partager sous forme de schémas évite bien des malentendus.
Organiser le dépôt
mon-app/
package.json scripts communs (workspaces npm)
client/ React + Vite
src/App.jsx
vite.config.js
server/ Express 5
src/app.js
src/server.js
{
"private": true,
"workspaces": ["client", "server"],
"scripts": {
"dev": "npm run dev -w server & npm run dev -w client",
"build": "npm run build -w client",
"test": "npm test -w server"
}
}
En développement, deux serveurs tournent : Vite sur http://localhost:5173 (rechargement à chaud) et l’API sur http://localhost:3000.
Le proxy de Vite
Pour le navigateur, localhost:5173 et localhost:3000 sont deux origines différentes (le port fait partie de l’origine). Le plus simple en développement est de demander à Vite de relayer les appels /api :
// client/vite.config.js
import { defineConfig } from "vite";
import react from "@vitejs/plugin-react";
export default defineConfig({
plugins: [react()],
server: {
proxy: { "/api": "http://localhost:3000" }, // /api/notes -> http://localhost:3000/api/notes
},
});
Le code React appelle alors simplement fetch("/api/notes") : même origine, aucun problème de CORS. En production, on reproduit la même chose en servant le client et l’API depuis le même domaine (leçon 5).
Quand faut-il CORS ?
La politique de même origine empêche un script d’une origine de lire les réponses d’une autre. Si votre client est sur app.exemple.fr et l’API sur api.exemple.fr, l’API doit l’autoriser explicitement avec des en-têtes CORS :
const ORIGINES = new Set(["https://app.exemple.fr", "http://localhost:5173"]);
app.use((req, res, next) => {
const origin = req.get("origin");
if (origin && ORIGINES.has(origin)) {
res.set("Access-Control-Allow-Origin", origin); // renvoyer l'origine autorisée
res.set("Access-Control-Allow-Credentials", "true"); // si l'on utilise des cookies
res.set("Vary", "Origin"); // le cache dépend de l'origine
}
if (req.method === "OPTIONS") { // requête préliminaire (preflight)
res.set("Access-Control-Allow-Methods", "GET,POST,PATCH,DELETE");
res.set("Access-Control-Allow-Headers", "Content-Type");
return res.status(204).end();
}
next();
});
Le paquet cors fait la même chose : app.use(cors({ origin: [...], credentials: true })). Deux règles : liste blanche d’origines, et jamais Access-Control-Allow-Origin: * avec des cookies (les navigateurs le refusent d’ailleurs). CORS protège les utilisateurs dans leur navigateur ; ce n’est pas un mécanisme d’authentification de l’API.
Variables d’environnement côté client
Vite n’expose au code React que les variables préfixées VITE_ (import.meta.env.VITE_API_URL). Elles sont intégrées au JavaScript livré : tout le monde peut les lire. Les secrets (clé d’API privée, mot de passe de base) restent exclusivement côté serveur.
À retenir
Deux programmes, un contrat HTTP/JSON. En développement, le proxy Vite évite CORS ; si les origines diffèrent en production, configurez CORS avec une liste blanche. Aucun secret dans le client.
Exercices
Les solutions et les tests restent sur le serveur. Dans l'espace étudiant, ton code est exécuté et corrigé, et le tuteur donne des indices avant la solution.
Middleware CORS avec liste blanche
Écrivez la fabrique
corsMiddleware(allowedOrigins)(Setd’origines) qui renvoie un middleware :- si l’en-tête
Origin(lu viareq.get("origin")) est dans la liste : définitAccess-Control-Allow-Originà cette origine,Access-Control-Allow-CredentialsàtrueetVaryàOrigin(viares.set) ; - une origine inconnue ne reçoit aucun en-tête CORS ;
- pour une requête
OPTIONS: définitAccess-Control-Allow-MethodsàGET,POST,PATCH,DELETEetAccess-Control-Allow-HeadersàContent-Type, puis répond204avecres.status(204).end()sans appelernext; - sinon appelle
next().
function corsMiddleware(allowedOrigins) { // À compléter }- si l’en-tête
Construire les URL de l'API côté client
Écrivez
apiUrl(base, path, params)qui construit l’URL d’un appel d’API :basepeut être""(même origine, proxy Vite) ou une URL comme"https://api.exemple.fr"(avec ou sans/final) ;pathcommence par/;params(facultatif) est un objet ; les valeursundefinedounullsont ignorées, les autres encodées avecURLSearchParams.
Exemple :
apiUrl("", "/api/notes", { q: "café", page: 2 })donne"/api/notes?q=caf%C3%A9&page=2".function apiUrl(base, path, params = {}) { // À compléter }Même origine ?
Le client tourne sur
http://localhost:5173et appellehttp://localhost:3000/api/notes. Est-ce la même origine ?- Oui, c'est le même domaine
- Non, le port diffère
- Oui, car les deux sont en HTTP
- Cela dépend du navigateur
Quiz de fin de leçon
Quel est le rôle du proxy Vite en développement ?
- Compiler le serveur Express
- Relayer les appels /api vers le serveur Node pour rester sur la même origine
- Chiffrer les requêtes
Une variable
VITE_STRIPE_SECRETest-elle sûre ?- Oui, Vite la chiffre
- Non, toute variable VITE_ est intégrée au code client et publique
- Oui, si le dépôt est privé
Que doit répondre un serveur à une requête preflight OPTIONS autorisée ?
- Le contenu de la ressource
- Les en-têtes Access-Control-Allow-* avec un statut 204 (ou 200)
- Une redirection
La suite du cours
- Consommer l'API depuis React : client HTTP, chargement et erreurs20 min
- Persister les données : l'API et sa base SQL20 min
- Authentification full-stack : sessions, cookies et routes protégées20 min
- Construire et mettre en production l'application full-stack18 min