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.

Overview

When a user interacts with StartER, each action follows a precise sequence: from the browser request to the response returned by the server.

  1. The browser sends a single HTTP request to the server (GET, POST, PUT, DELETE, etc.)
  2. The Express server parses and processes this request through a series of middleware
  3. The content is generated according to the corresponding path: JSON data for an API or a server-rendered HTML page
  4. The server sends an HTTP response to the browser (containing the JSON-formatted data or the rendered HTML page)
  5. 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.

Server architecture

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...
});

API request processing

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.

Routing

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);

Intermediate middleware

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
  • ...

Actions and responses

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.)

Page request processing

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.

SSR process

The process takes place in 4 steps in entry-server.tsx:

  1. Get a routing context

    const context = await query(
      new Request(`${req.protocol}://${req.get("host")}${req.originalUrl}`),
    );
  2. Create a static router for the SSR

    const router = createStaticRouter(dataRoutes, context);
  3. Render with a StaticRouterProvider

    const { pipe } = renderToPipeableStream(
      <StrictMode>
        <StaticRouterProvider router={router} context={context} />
      </StrictMode>,
    );
  4. 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:

Client-side hydration

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:

Where to go next

To delve deeper into the technical aspects of the framework, see the following pages:

⚠️ **GitHub.com Fallback** ⚠️