NodeJSThe Request Object

The Request Object

Express's req object extends Node's raw IncomingMessage with a wealth of convenient properties and helper methods — parsed params, query, body, cookies, content negotiation, and more. Everything the raw object had is still there; Express just adds the conveniences you'd otherwise build yourself. This page is the reference tour.

The most-used properties

Property

What it holds

req.params

Route parameters — { id: "42" }

req.query

Parsed query string — { sort: "name" }

req.body

Parsed body (needs body-parser middleware)

req.method

'GET', 'POST', …

req.path

Path without query string — /users/42

req.originalUrl

Full original URL incl. query

req.headers

All request headers (lowercased)

req.cookies

Parsed cookies (needs cookie-parser)

req.ip

Client IP address

req.protocol

'http' or 'https'

req.hostname

Host from the Host header

req.body needs middleware
`req.body` is `undefined` without a body parser
Unlike `req.params` and `req.query` (always available), `req.body` only exists if you registered body-parsing middleware *before* the route. `express.json()` for JSON, `express.urlencoded()` for HTML forms. Forget it, and `req.body` is `undefined` — destructuring it (`const { x } = req.body`) then throws "Cannot destructure property of undefined." This is the #1 Express beginner error.

JS
app.use(express.json())                       // JSON bodies → req.body
app.use(express.urlencoded({ extended: true }))// form bodies → req.body

app.post('/users', (req, res) => {
  const { name } = req.body                     // now populated
  res.status(201).json({ name })
})
Helper methods

Method

Returns

req.get(header)

A header value (case-insensitive)

req.accepts(types)

Best matching type the client accepts, or false

req.is(type)

Whether the request's Content-Type matches

req.param(name)

Look up params/body/query (legacy — avoid)

JS
req.get('Content-Type')        // 'application/json'
req.get('user-agent')          // case-insensitive header lookup

// Content negotiation — serve JSON or HTML based on what the client wants:
if (req.accepts('html')) {
  res.render('user', user)
} else if (req.accepts('json')) {
  res.json(user)
}

req.is('application/json')     // truthy if the body is JSON
Use `req.get()` instead of `req.headers[...]`
`req.get('Content-Type')` is case-insensitive and reads more clearly than `req.headers['content-type']`. Both work — `req.get` is the Express idiom. (Avoid the legacy `req.param(name)`, which searches params, body, and query in a confusing precedence order; read from the specific source instead.)
Client IP and proxies
`req.ip` is the PROXY's IP unless you enable `trust proxy`
When your app runs behind a reverse proxy or load balancer (the common production setup), the TCP connection comes *from the proxy*, so `req.ip` is the proxy's address, not the real client's. The true client IP is in the `X-Forwarded-For` header — but Express only trusts it if you set `app.set('trust proxy', ...)`. Get this wrong and rate-limiting, logging, and geo-features all see one IP for everyone.

JS
// Trust the first proxy in front of the app:
app.set('trust proxy', 1)

// Or trust specific networks. Then req.ip reflects X-Forwarded-For:
app.get('/whoami', (req, res) => res.json({ ip: req.ip, ips: req.ips }))
Don't blindly `trust proxy` — it lets clients spoof their IP
`X-Forwarded-For` is just a header a client can set. If you trust it without a real proxy in front (or trust too many hops), an attacker can forge their IP — defeating rate limits and IP allow-lists. Set `trust proxy` to match your *actual* infrastructure (the number of proxies, or their specific IPs), never a blanket `true` on an internet-facing app.
Cookies

JS
import cookieParser from 'cookie-parser'   // npm i cookie-parser
app.use(cookieParser())

app.get('/dashboard', (req, res) => {
  const sessionId = req.cookies.sessionId   // parsed from the Cookie header
  res.json({ sessionId })
})
`req.cookies` needs the `cookie-parser` middleware
Like `req.body`, parsed `req.cookies` isn't there by default — register `cookie-parser`. Without it you'd read and parse the raw `Cookie` header yourself. For signed cookies (tamper-evident), pass a secret to `cookieParser(secret)` and read `req.signedCookies`.
Extending req in middleware

A core pattern: middleware attaches computed data to req so later handlers can use it. This is how authentication exposes the current user:

JS
async function authenticate(req, res, next) {
  const token = req.get('authorization')?.replace('Bearer ', '')
  const user = await verifyToken(token)
  if (!user) return res.status(401).json({ error: 'Unauthorized' })
  req.user = user                 // ← downstream handlers read req.user
  next()
}

app.use(authenticate)
app.get('/me', (req, res) => res.json(req.user))
Attaching to `req` is the Express way to share request-scoped data
Because the same `req` object flows through the entire [middleware pipeline](/nodejs/middleware-intro), setting `req.user` (or `req.requestId`, `req.db`, etc.) in early middleware makes it available everywhere downstream — a clean, request-scoped way to pass data without globals. This pattern recurs throughout real Express apps.
Next
The counterpart — every way to build and send a response: [The Response Object](/nodejs/express-response).