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.
When a user interacts with the StartER application, here's what happens:
- The browser sends an HTTP request to the "one server" (GET, POST, PUT, DELETE, etc.)
- The Express server processes this request through various middleware.
- 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).
- The server sends an HTTP response to the browser (containing the data formatted as JSON 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 lifecycle can vary depending on the type of request (API or page) and the user's authentication status.
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...
});
Requests to the API (starting with /api
) are processed by Express using a modular routing system.
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);
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
- ...
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.)
For requests that don't match API routes, StartER uses React's Server-Side Rendering (SSR) to generate HTML 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
StaticRouterProvider
const { 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);
See the full code for details:
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:
Refer to the following pages for more practical aspects of the framework: