Request lifecycle en US - rocambille/start-express-react GitHub Wiki
The request-response cycle is at the heart of any web application. In StartER, it is supported by a unified architecture combining Express (API) and React (interface), all managed by one server.
When a user interacts with StartER, each action follows a precise sequence: from the browser request to the response returned by the server.
- The browser sends a single HTTP request to the server (GET, POST, PUT, DELETE, etc.)
- The Express server parses and processes this request through a series of middleware
- The content is generated according to the corresponding path: JSON data for an API or a server-rendered HTML page
- The server sends an HTTP response to the browser (containing the JSON-formatted data or the rendered HTML page)
- The React client hydrates (for web pages) and becomes interactive
ββββββββββββββββ 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) β
ββββββββββββββββ βββββββββββββββ
This diagram illustrates the flow of data between the browser, the Express server, the database, and the React SSR engine.
The cycle may vary depending on the type of request (API or page) and the user's authentication status.
The server.ts file is the entry point for the application. It configures a single Express server, responsible for:
- serving API routes
- rendering React pages via Server-Side Rendering (SSR).
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...
});Requests to the API (prefixed with /api) are handled by Express using a modular routing system.
Each module is isolated in src/express/modules, making the architecture easy to extend.
Routes are organized by modules in the src/express/modules folder and used in 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);
/* ************************************************************************ */Each module defines its own routes. For example, for the src/express/modules/item module in 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);Before reaching their final handler, requests pass through a chain of middleware:
- Rate limiting by IP address
- JSON parsing (
express.json()) - Authentication verification via JWT
- Data validation with
Zod - ...
Request handlers (actions) are responsible for final processing and response generation:
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);
};Responses to an API request can be:
- JSON objects (for GET and POST requests)
- HTTP status codes (for PUT and DELETE requests)
- Error codes (400, 401, 403, 404, etc.)
For all other requests (those not targeting /api), StartER uses React in Server-Side Rendering (SSR) mode to generate the initial HTML of the pages.
The process takes place in 4 steps in entry-server.tsx:
-
Get a routing context
const context = await query( new Request(`${req.protocol}://${req.get("host")}${req.originalUrl}`), );
-
Create a static router for the SSR
const router = createStaticRouter(dataRoutes, context);
-
Render with a
StaticRouterProviderconst { pipe } = renderToPipeableStream( <StrictMode> <StaticRouterProvider router={router} context={context} /> </StrictMode>, );
-
Send the HTML response to the client
res.status(200).set("Content-Type", "text/html; charset=utf-8"); res.write(htmlStart); // ... pipe(transformStream);
These four steps result in a complete HTML page sent to the browser, ready to be "rehydrated" on the client side.
See the full code for details:
Once the initial HTML is loaded, the client JavaScript takes over: it reactivates the React components already present in the page and makes the interface interactive.
hydrateRoot(
root,
<StrictMode>
<RouterProvider router={router} />
</StrictMode>,
);This hydration uses the data preloaded during the SSR to avoid repeating the initial queries.
See the full code for details:
To delve deeper into the technical aspects of the framework, see the following pages: