Le cycle requête réponse - rocambille/start-express-react GitHub Wiki
Le cycle requête-réponse est le fondement de toute application web. Dans StartER, ce cycle est géré par une architecture qui combine Express pour l'API et React pour l'interface utilisateur, le tout avec un serveur unique.
Lorsqu'un utilisateur interagit avec l'application StartER, voici ce qui se passe :
- Le navigateur envoie une requête HTTP au serveur unique (GET, POST, PUT, DELETE...)
- Le serveur Express traite cette requête à travers différents middlewares
- Le contenu résultat de la requête est généré selon le chemin déterminé (des données pour un appel d'API ou du contenu HTML pour une requête de page)
- Le serveur envoie une réponse HTTP au navigateur (contenant les données formatées en JSON ou la page HTML rendue)
- Le client React s'hydrate (pour les pages web) et devient interactif
┌──────────────┐ HTTP Request (1) ┌─────────────┐ Data Request ┌─────────────┐
│ │───────────────────> │ │───────────────────> │ │
│ Browser │ │ Express │ │ Database │
│ │ <───────────────────│ Server (2) │ <──────────────────│ │
└──────────────┘ HTTP Response (4) └─────────────┘ Data (3a) └─────────────┘
▲ ▲ │
│ │ │
│ HTML (3b) │ ▼ Page Request
┌──────────────┐ ┌─────────────┐
│ │ │ │
│ React Client │ <────Hydration───── │ React SSR │
│ (Browser) │ (5) │ (Server) │
└──────────────┘ └─────────────┘
Ce cycle peut varier selon le type de requête (API ou page) et l'état d'authentification de l'utilisateur.
Le point d'entrée de l'application est le fichier server.ts
, qui configure un serveur Express unique pour gérer à la fois l'API vers les données et le rendu des pages.
const app = express();
// Express API routes
app.use((await import("./src/express/routes")).default);
// Middleware "catch-all" for SSR
app.use(/(.*)/, async (req, res, next) => {
// SSR logic...
});
Les requêtes adressées à l'API (commençant par /api
) sont traitées par Express à travers un système de routage modulaire.
Les routes sont organisées par modules dans le dossier src/express/modules
et utilisées dans src/express/routes.ts
:
/* ************************************************************************ */
import authRoutes from "./modules/auth/authRoutes";
router.use(authRoutes);
/* ************************************************************************ */
import itemRoutes from "./modules/item/itemRoutes";
router.use(itemRoutes);
/* ************************************************************************ */
import userRoutes from "./modules/user/userRoutes";
router.use(userRoutes);
/* ************************************************************************ */
Chaque module définit ses propres routes. Par exemple, pour le module src/express/modules/item
dans itemRoutes.ts
:
const BASE_PATH = "/api/items";
const ITEM_PATH = "/api/items/:itemId";
router.get(BASE_PATH, itemActions.browse);
router.get(ITEM_PATH, itemActions.read);
router.post(BASE_PATH, itemValidator.validate, itemActions.add);
router.route(ITEM_PATH)
.all(checkAccess)
.put(itemValidator.validate, itemActions.edit)
.delete(itemActions.destroy);
Les requêtes API traversent plusieurs middlewares avant d'atteindre leur gestionnaire final, dont :
- Middleware de rate limiting pour limiter le nombre de requêtes par IP
-
express.json()
pour parser le corps des requêtes JSON - Middlewares d'authentification pour vérifier les tokens JWT (pour les routes protégées)
- Validateurs pour valider les données entrantes avec Zod
- ...
Les gestionnaires de requêtes (actions) sont responsables du traitement final et de la génération des réponses :
const add: RequestHandler = async (req, res) => {
const insertId = await itemRepository.create(req.body);
res.status(201).json({ insertId });
};
const read: RequestHandler = (req, res) => {
res.json(req.item);
};
Les réponses à une requête d'API peuvent être :
- Des objets JSON (pour les requêtes GET et POST)
- Des codes de statut HTTP (pour les requêtes PUT et DELETE)
- Des codes d'erreur (400, 403, 404...)
Pour les requêtes qui ne correspondent pas à des routes API, StartER utilise le Server-Side Rendering (SSR) de React pour générer les pages HTML.
Le processus se déroule en 4 étapes dans entry-server.tsx
:
-
Obtenir un contexte de routage
const context = await query( new Request(`${req.protocol}://${req.get("host")}${req.originalUrl}`), );
-
Créer un routeur statique pour le SSR
const router = createStaticRouter(dataRoutes, context);
-
Faire le rendu avec
StaticRouterProvider
const { pipe } = renderToPipeableStream( <StrictMode> <StaticRouterProvider router={router} context={context} /> </StrictMode>, );
-
Envoyer la réponse HTML au client
res.status(200).set("Content-Type", "text/html; charset=utf-8"); res.write(htmlStart); // ... pipe(transformStream);
Voir le code complet pour les détails :
Une fois que le navigateur a reçu le HTML initial, le JavaScript client prend le relais pour "hydrater" l'application et la rendre interactive :
hydrateRoot(
root,
<StrictMode>
<RouterProvider router={router} />
</StrictMode>,
);
Cette hydratation utilise les données pré-chargées pendant le SSR pour éviter de refaire les requêtes initiales.
Voir le code complet pour les détails :
Référez-vous aux pages suivantes pour les aspects plus pratiques du framework :