req is the inbox. Whatever the client sent — URL params, JSON body, headers, cookies — Express parses it (with a little help from middleware) and parks it on this object. In simple language: req is everything we know about who’s calling and what they want.
Think of an HTTP request like a letter: there’s an address (URL), a return address (IP), some envelope notes (headers), and the actual letter inside (body). Express splits all of that across different properties on req.
params, query, path
body
headers, cookies
ip, method, protocol
req.params — bits from the URL path
When we define a route with :something, Express captures it.
app.get("/users/:userId/posts/:postId", (req, res) => {
// GET /users/42/posts/7
req.params.userId; // "42"
req.params.postId; // "7"
});
Params are always strings. If we need a number, parseInt(req.params.userId, 10).
req.query — the ?key=value part
// GET /search?q=express&limit=10&tags=node&tags=web
app.get("/search", (req, res) => {
req.query.q; // "express"
req.query.limit; // "10" (string, not number)
req.query.tags; // ["node", "web"] — repeated keys become arrays
});
Express uses the qs library by default, so nested queries like ?filter[age]=30 parse into { filter: { age: "30" } }. Set app.set("query parser", "simple") for the basic version.
req.body — the request payload
We get nothing here until we install body-parsing middleware:
app.use(express.json()); // for application/json
app.use(express.urlencoded({ extended: true })); // for form posts
app.post("/users", (req, res) => {
// POST /users with body: {"name": "Manish", "email": "m@x.com"}
req.body.name; // "Manish"
req.body.email; // "m@x.com"
});
Without express.json(), req.body is undefined. This is the #1 “why is body empty” gotcha.
req.headers — request headers (lowercased)
app.get("/me", (req, res) => {
req.headers["authorization"]; // "Bearer eyJhbGc..."
req.headers["user-agent"]; // browser string
req.headers["content-type"]; // "application/json"
// Shortcut for any header
req.get("Authorization"); // same as above, case-insensitive
});
All header names are lowercased — req.headers.Authorization returns undefined. Use req.get() if we want case-insensitive lookup.
req.cookies — needs cookie-parser
const cookieParser = require("cookie-parser");
app.use(cookieParser());
app.get("/dashboard", (req, res) => {
req.cookies.session; // "abc123..."
});
Without cookie-parser, cookies live in req.headers.cookie as one long string we’d have to parse ourselves.
req.ip — the client’s IP
app.get("/whoami", (req, res) => {
res.json({ ip: req.ip });
});
Behind a proxy (nginx, Caddy, Cloudflare), req.ip shows the proxy’s IP, not the real client. Fix by trusting the proxy:
app.set("trust proxy", 1); // trust first proxy in chain
// Now req.ip reads from X-Forwarded-For
req.path vs req.url vs req.originalUrl
// App mounted at /api, request to /api/users?active=true
req.path; // "/users"
req.url; // "/users?active=true"
req.originalUrl; // "/api/users?active=true" ← full URL before mount stripping
req.originalUrl is the one we want for logging — it shows what the client actually requested.
Bonus useful ones
req.method; // "GET", "POST", etc.
req.protocol; // "http" or "https"
req.hostname; // "api.example.com"
req.secure; // true if https
req.xhr; // true if X-Requested-With: XMLHttpRequest
Putting it all together — a logging middleware:
app.use((req, res, next) => {
console.log(`${req.method} ${req.originalUrl} from ${req.ip}`);
next();
});
That’s most of what we’ll ever need from req.