# Request/Response Lifecycle

## 1. From Socket to Handler and Back

Every framework request follows the same overall arc, even though the names differ:

```
TCP connection accepted
  → HTTP parser reads request line + headers   (Node's llhttp)
  → framework creates req/res objects
  → global middleware                           (06-middleware/)
  → routing: find the handler                   (04-routing/)
  → route-level middleware / guards / pipes
  → handler (controller)                        (05-controllers/)
  → response serialization (JSON, status, headers)
  → bytes written to the socket
  → "finish" event, logging, metrics
```

Understanding this order answers most "why didn't X happen?" questions: the body parser hadn't run yet; the guard ran before the pipe; the logger was registered after the router; headers were already sent.

## 2. The Express Version

```ts
const app = express();
app.use(helmet());           // 1. security headers on every response
app.use(express.json());     // 2. stream + parse the body into req.body
app.use(requestId);          // 3. attach an id for logs
app.use("/api", authenticate);
app.get("/api/orders/:id", loadOrder, (req, res) => res.json(res.locals.order)); // route + handler
app.use(notFound);           // 4. nothing matched → 404
app.use(errorHandler);       // 5. anything called next(err) → error response
```

Express has exactly one concept — middleware — and the router itself is middleware. The "lifecycle" is literally the order of `app.use`/`app.METHOD` calls.

## 3. The NestJS Version

Nest formalizes the stages so each kind of logic has a fixed slot:

```
Incoming request
  → Middleware            (global, then module-bound)
  → Guards                (global → controller → route)
  → Interceptors, before  (global → controller → route)
  → Pipes                 (global → controller → route → param)
  → Controller method
  → Service(s)
  → Interceptors, after   (route → controller → global — reverse order)
  → Exception filters     (route → controller → global — only if something threw)
  → Response
```

| If you need to... | Use | Because it runs... |
|---|---|---|
| reject unauthenticated/unauthorized calls | Guard | before any parsing/validation work |
| validate & transform `@Body()`/`@Param()` | Pipe | right before the handler, per argument |
| time, cache, or reshape the response | Interceptor | around the handler (before *and* after) |
| map exceptions to HTTP errors | Exception filter | only on the error path |

## 4. The Body Is a Stream

The request body is **not** available when the request object is created — it arrives as a stream of chunks after the headers. A body parser must:

1. check `Content-Type` (`application/json`, `multipart/form-data`, ...)
2. read chunks until the end, enforcing a **size limit** (Express default `100kb`)
3. decode and parse, then set `req.body` and call `next()`

That's why a handler that reads `req.body` before a parser has run sees `undefined`, and why huge uploads are streamed to disk instead (`15-file_uploads/`).

## 5. The Response Side

A response has three phases and the order is enforced by HTTP itself:

1. **Status line + headers** — can be changed freely *until* the first byte is written.
2. **Body** — once writing starts, headers are flushed (`res.headersSent === true`) and can no longer change.
3. **End** — `res.end()`; Node emits `"finish"` when the last byte is handed to the OS.

```ts
res.status(201);
res.setHeader("Location", `/orders/${order.id}`);
res.json(order);          // serializes, sets Content-Type + Content-Length, writes, ends
res.setHeader("X-Late", "1"); // ❌ ERR_HTTP_HEADERS_SENT
```

## 6. Where Request-Scoped Data Lives

Data produced early in the lifecycle (the authenticated user, a request ID, a DB transaction) needs to reach code that runs later:

| Mechanism | Example | Notes |
|---|---|---|
| request object | `req.user`, `res.locals.order` | simplest; must be passed down manually |
| AsyncLocalStorage | `als.getStore().requestId` | available anywhere in the same async call chain, even in services |
| DI request scope | Nest `@Injectable({ scope: Scope.REQUEST })` | a new provider instance per request; slower, use sparingly |

```ts
import { AsyncLocalStorage } from "node:async_hooks";
const als = new AsyncLocalStorage<{ requestId: string }>();

app.use((req, res, next) => als.run({ requestId: crypto.randomUUID() }, next));

function log(msg: string) {
  console.log(`[${als.getStore()?.requestId}] ${msg}`); // works deep inside services
}
```

## Under the Hood: Node's HTTP Server and the Event Loop

`http.createServer(handler)` registers a listener for the `"request"` event. For each connection, Node's C-based **llhttp** parser consumes bytes from the socket; once the header block is complete it constructs an `IncomingMessage` (a readable stream — the body hasn't arrived yet) and a `ServerResponse` (a writable stream), and emits `"request"`. Express's `app` *is* that handler function: it wraps `req`/`res` with its prototypes and starts the middleware chain.

Everything then runs on **one thread**. While one handler awaits a database query, the event loop is free to parse and dispatch other requests — which is why a CPU-heavy synchronous handler (say, a 200ms JSON transform) delays *every* concurrent request, not just its own. Keep-alive lets one TCP connection carry many sequential requests; `server.keepAliveTimeout` and `headersTimeout` bound how long idle or slow clients can hold sockets.

AsyncLocalStorage works because Node tracks **async resources**: every promise, timer and I/O callback created while a store is active inherits that store, so `getStore()` in a callback many awaits later returns the context of the request that started the chain.

![A request flows from socket parsing through middleware, guards, interceptors, pipes and the handler, then back out through interceptors and exception filters before the response is written](diagrams/01-request-lifecycle.svg)

## Common Pitfalls

1. **Reading `req.body` without a body parser** (or with the wrong `Content-Type`) → `undefined`.
2. **Setting headers after the body has started** → `ERR_HTTP_HEADERS_SENT`.
3. **Validation in a guard, authorization in a pipe** — each stage has a job; mixing them makes ordering bugs (e.g. validating a body before rejecting an unauthenticated caller wastes work and leaks validation details).
4. **CPU-bound work inside a handler** blocks every other in-flight request (move it to `node/09-worker_threads` or a background job, `16-background_jobs/`).
5. **Not responding on every code path** — a branch that neither responds nor throws leaves the request hanging.
6. **Module-level mutable state used as "request state"** — shared by every concurrent request; use the request object or AsyncLocalStorage.

## Quick Reference

| Stage | Express | NestJS |
|---|---|---|
| cross-cutting pre-processing | `app.use(mw)` | middleware |
| allow/deny | middleware | guard |
| validate/transform input | middleware (zod/joi) | pipe |
| around-the-handler logic | `res.on("finish")` | interceptor |
| business logic | handler → service | controller → service |
| errors → responses | 4-arg error middleware | exception filter |
| request-scoped data | `req`, `res.locals`, ALS | `@Req()`, ALS, request-scoped providers |
