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.
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 |
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.
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:
app.set("query parser", "extended"); // uses the qs libraryBecause req.query cannot be reassigned, middleware that mutates it will throw. Copy values into res.locals or a custom property instead.
Response methods are chainable, and each request is finished by exactly one terminal call (send, json, end, sendFile, redirect, render, sendStatus).
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.| 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 |
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).In Express 5, what is `req.body` when no body-parsing middleware has run?
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".json, send, sendFile or redirect.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.