Request lifecycle en US - rocambille/start-express-react GitHub Wiki

The request lifecycle is the foundation of any web application. In StartER, this cycle is managed by an architecture that combines Express for the API and React for the user interface, all in one server.

Overview

When a user interacts with the StartER application, here's what happens:

  1. The browser sends an HTTP request to the "one server" (GET, POST, PUT, DELETE, etc.)
  2. The Express server processes this request through various middleware.
  3. The resulting content of the request is generated according to the specified path (data for an API call or HTML content for a page request).
  4. The server sends an HTTP response to the browser (containing the data formatted as JSON 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 lifecycle can vary depending on the type of request (API or page) and the user's authentication status.

Server architecture

The application's entry point is the server.ts file, which configures a single Express server to handle both the API to data and page rendering.

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 (starting with /api) are processed by Express using a modular routing system.

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

API requests pass through several middlewares before reaching their final handler, including:

  • Rate limiting middleware to limit the number of requests per IP
  • express.json() to parse the JSON request body
  • Authentication middleware to verify JWT tokens (for protected routes)
  • Validators to validate incoming data 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, 403, 404, etc.)

Page request processing

For requests that don't match API routes, StartER uses React's Server-Side Rendering (SSR) to generate HTML 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);

See the full code for details:

Client-side hydration

Once the browser receives the initial HTML, the client-side JavaScript takes over to hydrate the application and make it 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

Refer to the following pages for more practical aspects of the framework:

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