Request and Response Objects in Depth

Beginner
12 min

Request and Response Objects in Depth

Every Express handler receives two objects: req, which describes the incoming HTTP request, and res, which you use to build the reply. In this lesson you will go beyond req.params and res.send(): reading headers, inspecting the URL, sending files, redirecting, and the Express 5 rules that make these objects safer than in Express 4.

This course uses ES modules ("type": "module" in package.json), so examples use import syntax.

Reading the request

req is Node's http.IncomingMessage with extra properties added by Express. The most useful ones:

| Property / method | What it gives you | | --- | --- | | req.method | GET, POST, PUT, ... | | req.path | Path part of the URL, without the query string | | req.originalUrl | Full URL as received, including the query string | | req.hostname / req.host | Host name; in Express 5 req.host keeps the port | | req.ip / req.ips | Client address (honours trust proxy when set) | | req.get(name) | Read a header, case-insensitive | | req.is(type) | Check the request Content-Type | | req.accepts(types) | Pick the best response type the client accepts |

javascript
app.post("/upload-info", (req, res) => { if (!req.is("application/json")) { return res.status(415).send("Send JSON"); } const lang = req.get("Accept-Language") ?? "unknown"; res.json({ receivedFrom: req.ip, lang, via: req.originalUrl }); });

req.body is only populated after a body-parsing middleware such as express.json() runs. In Express 5 it is undefined (not {}) when nothing parsed the body, so use req.body?.title when a parser may not have run.

Query parsing in Express 5

In Express 5, req.query is a read-only getter backed by the query parser setting. The default is "simple", which uses Node's built-in parser and does not turn ?filter[status]=done into nested objects. Opt in explicitly if you need that:

javascript
app.set("query parser", "extended"); // uses the qs library

Because req.query cannot be reassigned, middleware that mutates it will throw. Copy values into res.locals or a custom property instead.

Sending responses

Response methods are chainable, and each request is finished by exactly one terminal call (send, json, end, sendFile, redirect, render, sendStatus).

javascript
app.get("/report", (req, res) => { res .status(201) .set({ "Cache-Control": "no-store", "X-Request-Id": "abc123" }) .send({ ok: true }); }); app.get("/health", (req, res) => res.sendStatus(204)); app.get("/invoice.pdf", (req, res) => res.download("./files/invoice.pdf", "your-invoice.pdf") ); app.get("/logo", (req, res) => res.sendFile("logo.png", { root: "./public/img" }) );
  • res.send() sets Content-Type automatically: strings become text/html, objects become JSON, Buffers become application/octet-stream.
  • res.json() always serialises with JSON.stringify and sets application/json.
  • res.sendFile() needs an absolute path or the root option.
  • res.redirect(status, url) defaults to 302; use 301 for permanent moves.
  • res.format({ "text/html": fn, "application/json": fn }) picks a handler from the Accept header, so one route can serve browsers and API clients.

What Express 5 removed or tightened

| Express 4 | Express 5 replacement | | --- | --- | | res.send(404) | res.sendStatus(404) | | res.json(obj, 201) | res.status(201).json(obj) | | res.redirect("back") | res.redirect(req.get("Referrer") ?? "/") | | req.param("id") | req.params.id, req.query.id or req.body.id | | res.status("200") | Integer between 100 and 999, otherwise it throws |

Common mistakes

  • Calling res.send() twice in one handler raises ERR_HTTP_HEADERS_SENT. Always return after the terminal call in early-exit branches, and check res.headersSent in shared error paths.
  • req.ip shows your proxy's address on Render, Railway or behind Nginx until you call app.set("trust proxy", 1).
Quick Quiz
Question 1 of 3

In Express 5, what is `req.body` when no body-parsing middleware has run?

Key Takeaways

  • req exposes the method, URL parts, headers, client address and content-type helpers such as req.is() and req.accepts().
  • req.query is a read-only getter in Express 5 and uses the simple parser unless you opt into "extended".
  • Response methods chain; finish each request with exactly one terminal call such as json, send, sendFile or redirect.
  • Express 5 removed ambiguous overloads (res.send(status), res.json(obj, status), res.redirect("back")) and validates status codes.

Next lesson: Middleware Explained — learn how functions run between the request and your route handler, and how next() chains them together.

Request and Response Objects in Depth - Express.js | CodeYourCraft | CodeYourCraft