A GET request's data lives entirely in the URL — route parameters and the query string, both covered already. A POST request typically carries data in its body instead, which needs its own step to read.
express.json(), from the middleware lesson, reads a JSON request body and parses it onto req.body:
app.use(express.json())
app.post('/products', (req, res) => {
console.log(req.body) // { name: 'Laptop Stand', price: 25 }
res.status(201).send(`Created: ${req.body.name}`)
})Without express.json() registered, req.body is simply undefined — this is the single most common reason a POST route "isn't receiving any data" while testing.
A plain HTML <form> doesn't send JSON by default — it sends application/x-www-form-urlencoded data instead, which needs a different piece of middleware:
<form method="POST" action="/contact">
<input name="email" type="email">
<textarea name="message"></textarea>
<button type="submit">Send</button>
</form>app.use(express.urlencoded({ extended: true }))
app.post('/contact', (req, res) => {
console.log(req.body) // { email: '...', message: '...' }
res.send('Thanks for your message')
})Registering both express.json() and express.urlencoded() at once is normal — each only activates for requests whose Content-Type header matches what it handles, so they don't conflict.
res.json() is the response-side equivalent — it sets the correct Content-Type and converts a JavaScript object to a JSON string automatically:
app.get('/products/:id', (req, res) => {
res.json({ id: req.params.id, name: 'Laptop Stand', price: 25 })
})res.send() works for JSON too, technically, but res.json() is more explicit about intent and handles a couple of edge cases (like sending null) more predictably. Use res.json() whenever the response is meant to be data rather than text or HTML.