Request Object

beginner express request

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.

req
URL bits
params, query, path
Payload
body
Metadata
headers, cookies
Network
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.

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.