Architecture full-stack : client React, API Node et CORS

Première leçon du cours Application full-stack React + Node, en accès libre : environ 18 minutes, 3 exercices et 3 questions de quiz. C'est la leçon de base du catalogue ; dans l'espace étudiant, elle est réécrite pour ton profil.

Objectifs

  • Organiser un projet full-stack avec un client React (Vite) et une API Express
  • Faire communiquer client et serveur en développement grâce au proxy Vite
  • Comprendre et configurer CORS quand client et API sont sur des origines différentes

Architecture full-stackProxy de développementCORSContrat d'API

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.

  1. CodeDifficulté 2JavaScript dans le navigateur2 indices

    Middleware CORS avec liste blanche

    Écrivez la fabrique corsMiddleware(allowedOrigins) (Set d’origines) qui renvoie un middleware :

    • si l’en-tête Origin (lu via req.get("origin")) est dans la liste : définit Access-Control-Allow-Origin à cette origine, Access-Control-Allow-Credentials à true et Vary à Origin (via res.set) ;
    • une origine inconnue ne reçoit aucun en-tête CORS ;
    • pour une requête OPTIONS : définit Access-Control-Allow-Methods à GET,POST,PATCH,DELETE et Access-Control-Allow-Headers à Content-Type, puis répond 204 avec res.status(204).end() sans appeler next ;
    • sinon appelle next().
    function corsMiddleware(allowedOrigins) {
      // À compléter
    }
  2. CodeDifficulté 1JavaScript dans le navigateur2 indices

    Construire les URL de l'API côté client

    Écrivez apiUrl(base, path, params) qui construit l’URL d’un appel d’API :

    • base peut être "" (même origine, proxy Vite) ou une URL comme "https://api.exemple.fr" (avec ou sans / final) ;
    • path commence par / ;
    • params (facultatif) est un objet ; les valeurs undefined ou null sont ignorées, les autres encodées avec URLSearchParams.

    Exemple : apiUrl("", "/api/notes", { q: "café", page: 2 }) donne "/api/notes?q=caf%C3%A9&page=2".

    function apiUrl(base, path, params = {}) {
      // À compléter
    }
  3. QCMDifficulté 11 indice

    Même origine ?

    Le client tourne sur http://localhost:5173 et appelle http://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

  1. 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
  2. Une variable VITE_STRIPE_SECRET est-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é
  3. 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

  1. Consommer l'API depuis React : client HTTP, chargement et erreurs20 min
  2. Persister les données : l'API et sa base SQL20 min
  3. Authentification full-stack : sessions, cookies et routes protégées20 min
  4. Construire et mettre en production l'application full-stack18 min

Application full-stack React + Node · Avancé · 4 h 36