Learn what the Express req object holds, from the URL, params and query to headers, IP address and body, and how to read each piece safely in Express 5.

Bishal Biswas
Self Learner
What you will learn
- What the request object (
req) is and where it comes from - How to read the method, URL, path, params and query string
- How to read headers, the client’s IP address and the host name
- Why
req.bodyisundefineduntil you add a body parser - Handy helpers like
req.get(),req.is()andreq.accepts()
You have already used req.params and req.query in the routing lessons. This lesson gives you the full tour.
What is the request object?
Every time a client sends a request, Express builds a JavaScript object that describes it. That object is passed to your handler as the first argument. By convention it is called req.
Think of it like an envelope arriving at a post office. The envelope has an address (the URL), a stamp that says how it was sent (the method), notes written on the outside (the headers), and maybe a letter inside (the body). req lets you read all of it.
app.get('/hello', (req, res) => {
// req = everything the client sent
// res = the tool you use to answer
res.send('Hi!');
});
Note:
reqis Node’s ownhttp.IncomingMessageobject with extra Express properties and methods added on top. That is why some properties (likereq.headers) come from Node and others (likereq.query) come from Express.
A quick tour: logging a request
The fastest way to learn req is to print it. Create this file and run it.
// app.js
import express from 'express';
const app = express();
app.get('/products/:id', (req, res) => {
res.json({
method: req.method,
url: req.url,
path: req.path,
params: req.params,
query: req.query,
hostname: req.hostname,
ip: req.ip,
});
});
app.listen(3000, (error) => {
if (error) throw error;
console.log('Listening on http://localhost:3000');
});
Now send a request with curl:
curl "http://localhost:3000/products/42?color=red&size=m"
You should see something like this (formatted for reading):
{
"method": "GET",
"url": "/products/42?color=red&size=m",
"path": "/products/42",
"params": { "id": "42" },
"query": { "color": "red", "size": "m" },
"hostname": "localhost",
"ip": "::1"
}
Let’s walk through each part.
The method
req.method is the HTTP method (the verb) as an uppercase string: 'GET', 'POST', 'PUT', 'PATCH' or 'DELETE'.
When you use app.get() or app.post(), Express already checked the method for you. You mostly use req.method in code that runs for every request, like a logger.
console.log(`${req.method} ${req.url}`); // GET /products/42?color=red&size=m
The URL, path and original URL
Express gives you a few slightly different views of the URL. It is easy to mix them up, so here they are side by side for the request above.
| Property | Value | What it is |
|---|---|---|
req.url |
/products/42?color=red&size=m |
Path plus query string |
req.originalUrl |
/products/42?color=red&size=m |
The URL exactly as the client sent it |
req.path |
/products/42 |
Only the path, no query string |
req.baseUrl |
'' (empty) |
The prefix a router is mounted on |
Right now req.url and req.originalUrl look identical. They start to differ when you split routes into routers mounted on a prefix such as /api. You will see that in express.Router and Modular Routes.
Tip: When you log requests, use
req.originalUrl. It always shows the full URL the client asked for, even inside a router.
Route parameters: req.params
Route parameters are the named parts of a path, like :id. Express puts their values in req.params.
app.get('/users/:userId/posts/:postId', (req, res) => {
const { userId, postId } = req.params;
res.send(`Post ${postId} by user ${userId}`);
});
curl http://localhost:3000/users/7/posts/99
# Post 99 by user 7
Remember two things from the routing lessons:
- Params are always strings.
req.params.idis'42', not42. Convert it withNumber()when you need a number. - A named wildcard such as
'/files/*filepath'gives you an array of path segments inreq.params.filepath.
app.get('/products/:id', (req, res) => {
const id = Number(req.params.id);
if (!Number.isInteger(id)) {
return res.status(400).send('id must be a whole number');
}
res.send(`Product number ${id}`);
});
Note: Express 4 had a
req.param('name')method that looked in params, body and query all at once. It was removed in Express 5. Read fromreq.params,req.queryorreq.bodydirectly, so you always know where a value came from.
The query string: req.query
The query string is the part of the URL after ?. Express parses it into req.query.
curl "http://localhost:3000/search?q=shoes&tag=sale&tag=new"
app.get('/search', (req, res) => {
console.log(req.query);
// { q: 'shoes', tag: [ 'sale', 'new' ] }
res.json(req.query);
});
Values are strings, and a repeated key becomes an array of strings. A missing key is simply undefined, so give it a default:
const page = Number(req.query.page ?? 1);
In Express 5, req.query is a read-only getter. A getter is a property that is calculated when you read it, instead of being stored. You cannot replace req.query with a new object. Copy the values you need into your own variables instead.
// Wrong in Express 5: this throws a TypeError
// req.query = { page: 1 };
// Right: build your own object
const filters = {
page: Number(req.query.page ?? 1),
q: req.query.q ?? '',
};
The query parser itself was covered in Query Strings and the Query Parser.
Headers: req.headers and req.get()
Headers are extra pieces of information the client sends with every request, written as Name: value pairs. For example, which browser it is, what format it wants back, or a login token.
Node stores them in req.headers. All header names are lowercase there.
app.get('/whoami', (req, res) => {
console.log(req.headers);
res.send('Check your terminal');
});
{
host: 'localhost:3000',
'user-agent': 'curl/8.7.1',
accept: '*/*'
}
The easier way is req.get(). It is case-insensitive, so you do not have to remember the lowercase rule.
app.get('/whoami', (req, res) => {
const agent = req.get('User-Agent');
const auth = req.get('Authorization'); // undefined if not sent
res.json({ agent, hasAuth: Boolean(auth) });
});
Send your own header with curl -H:
curl -H "Authorization: Bearer abc123" http://localhost:3000/whoami
# {"agent":"curl/8.7.1","hasAuth":true}
Tip:
req.header('X-Name')is an alias forreq.get('X-Name'). Pick one and stick to it. This course usesreq.get().
Who is asking? IP, host and protocol
These properties tell you about the connection itself.
| Property | Example value | Meaning |
|---|---|---|
req.ip |
'::1' |
The client’s IP address |
req.ips |
[] |
List of IPs from proxy headers (only with trust proxy) |
req.hostname |
'localhost' |
Host name, without the port |
req.host |
'localhost:3000' |
Host name, with the port (Express 5) |
req.protocol |
'http' |
'http' or 'https' |
req.secure |
false |
true when the protocol is https |
req.subdomains |
[] |
For api.shop.example.com, this is ['api'] |
On your own machine, req.ip is often ::1 or ::ffff:127.0.0.1. Both mean “this computer” (localhost), written in the IPv6 format.
Note: In production your app usually sits behind a reverse proxy, a server like Nginx that receives traffic first and passes it on. Then
req.ipshows the proxy’s address, not the real visitor’s. The fix isapp.set('trust proxy', 1), explained in Reverse Proxies and Trusting Proxy Headers.
The body: req.body
The body is the data sent inside a POST, PUT or PATCH request, such as a JSON object or form fields.
Here is the surprise for many beginners. This code does not work yet:
// app.js (body is not parsed yet)
app.post('/notes', (req, res) => {
console.log(req.body); // undefined
res.send('ok');
});
The body arrives as a stream of raw bytes. Express does not read it unless you ask. Until then, req.body is undefined in Express 5.
One line fixes it for JSON:
// app.js
app.use(express.json()); // read JSON bodies into req.body
app.post('/notes', (req, res) => {
console.log(req.body); // { title: 'Shopping', text: 'Milk' }
res.status(201).json(req.body);
});
curl -X POST http://localhost:3000/notes \
-H "Content-Type: application/json" \
-d '{"title":"Shopping","text":"Milk"}'
app.use() adds middleware, a function that runs before your routes. You will learn exactly how that works in How the Middleware Stack Works, and all the body parsers in Built-in Body Parsers.
Common mistake: Writing
const { title } = req.body;without a body parser. In Express 5 this throwsCannot destructure property 'title' of 'req.body' as it is undefined. Add the parser, or writeconst { title } = req.body ?? {};to be safe.
Content helpers: req.is() and req.accepts()
Two small helpers answer common questions.
req.is(type) checks what the client sent. It looks at the Content-Type header.
app.post('/upload', (req, res) => {
if (!req.is('application/json')) {
return res.status(415).send('Please send JSON');
}
res.send('Thanks for the JSON');
});
It returns the matching type (a truthy string), false if it does not match, or null if there is no body.
req.accepts(types) checks what the client wants back. It reads the Accept header and returns the best match, or false.
app.get('/greeting', (req, res) => {
const best = req.accepts(['html', 'json']);
if (best === 'json') {
return res.json({ message: 'Hello' });
}
res.send('<h1>Hello</h1>');
});
curl -H "Accept: application/json" http://localhost:3000/greeting
# {"message":"Hello"}
Related helpers use plural names in Express 5: req.acceptsLanguages(), req.acceptsCharsets() and req.acceptsEncodings(). The old singular names were removed.
const lang = req.acceptsLanguages('en', 'fr', 'de') || 'en';
Other properties you will meet later
You do not need these yet, but it helps to know they exist.
| Property | Filled in by | Lesson |
|---|---|---|
req.cookies |
cookie-parser |
Cookies with cookie-parser |
req.session |
express-session |
Sessions with express-session |
req.file |
multer |
File Uploads with Multer |
req.user |
Your auth code or Passport | JWT Authentication |
Notice the pattern: Express starts req small, and middleware adds properties to it as the request moves along. You will write your own middleware that does this soon.
Common mistakes
Treating params and query values as numbers. They are strings. '10' > '9' is false because strings compare letter by letter. Convert with Number() first.
Expecting a value in the wrong place. Data in the URL path is in req.params. Data after ? is in req.query. Data in a POST body is in req.body. Each has its own box.
Reading headers with the wrong case. req.headers['Content-Type'] is undefined because Node stores names in lowercase. Use req.get('Content-Type').
Trusting what the client sends. Everything in req comes from the outside world and can be faked. Always check it before you use it. You will learn proper validation in Request Validation with Zod.
Key takeaways
reqdescribes the incoming request: method, URL, headers, connection details and body.- Use
req.paramsfor path parts,req.queryfor the query string, andreq.bodyfor the body. All values from the URL are strings. req.queryis a read-only getter in Express 5. Copy values out instead of changing it.- Read headers with
req.get(), which ignores upper and lower case. req.bodyisundefineduntil a body parser such asexpress.json()runs.- Middleware can add new properties to
req, likereq.cookiesorreq.user.
Practice
- Build a
GET /debugroute that returnsreq.method,req.originalUrl,req.queryand theUser-Agentheader as JSON. Call it from your browser and fromcurland compare the results. - Create
GET /add/:a/:bthat returns the sum of the two numbers. Make sure/add/2/3returns5, not23. - Write
GET /hellothat answers in French whenAccept-Languageprefersfr, and in English otherwise. Test it withcurl -H "Accept-Language: fr". - Add
app.use(express.json())and aPOST /echoroute that sendsreq.bodyback. Then remove the parser line and see what happens.
What’s next
You can now read everything a client sends. Next, learn the other half of the conversation in The Response Object.
Was this explanation helpful?
Your feedback helps us improve the content for everyone.
Thank you for your feedback!
We appreciate you helping us improve.
What could we improve? (Optional)
Read aloud
Ready