Routes and bodies
Read request bodies with express.json(), create, change and delete cats, and declare the routes one path shares once with app.route(). Plus what Express 5 leaves in req.body when there is nothing to parse.
What you'll learn
- Mount express.json() so JSON request bodies arrive in req.body, and handle a body that was not parsed
- Declare several methods on one path with app.route()
- Answer a create with res.status(201).location(…).json(…) and a delete with res.sendStatus(204)
Reading data is half an API. The other half receives it: a new cat in a POST, a change in a PATCH, and those arrive in the request's body, which Express does not read on its own. This lesson adds the body parser, the routes that change the collection, and a tidier way to declare several methods on one path.
The body is not parsed until you ask
A request body arrives as a stream of bytes. Express leaves it alone unless a body parser reads it, and the one for JSON ships with Express:
app.use(express.json());
app.use() runs something for every request, before the routes that come after it; lesson 3 is about what else it can run. express.json() reads the body of any request whose Content-Type is application/json, parses it, and puts the result on req.body. There are siblings for other formats: express.urlencoded() for HTML forms, express.text() for plain text, express.raw() for bytes.
Two details decide whether a handler can trust req.body:
- Order matters. The parser must be mounted before the routes that read the body. Mounted after them, it runs too late.
- In Express 5,
req.bodyisundefinedwhen no parser read the body: no body, aContent-Typethe parser does not handle, or no parser at all. Express 4 used to leave an empty object there. Soconst { name } = req.bodythrows on a request that sent text, and the client gets a 500 for its own mistake. Destructuring fromreq.body ?? {}makes a missing body look like an empty one, which the validation after it then refuses.
Changing the collection
The routes follow the HTTP course's rules, written in Express:
app.post('/owners', (req, res) => {
const owner = { id: nextId(), ...req.body };
owners.push(owner);
res.status(201).location(`/owners/${owner.id}`).json(owner);
});
res.location(url) sets the Location header and, like status(), returns res, so the three calls chain. A delete that has nothing to say answers res.sendStatus(204), which sets the status and ends the response with no body; res.status(204).end() is the same. Validation belongs before the change: check what arrived, answer 400 and return if it is wrong, and only then store anything.
One path, several methods: app.route()
GET, PATCH and DELETE on /cats/:id share the path, and writing it three times invites a typo in one of them. app.route(path) names the path once and chains a handler per method:
app
.route('/owners/:id')
.get((req, res) => { /* read */ })
.put((req, res) => { /* replace */ })
.delete((req, res) => { /* remove */ });
Each handler is an ordinary route handler; only the declaration is shared. A method the route does not list falls through to Express's 404, as if the route did not exist.
Your task
- Parse JSON request bodies for every route.
POST /catscreates a cat from the body'sname(a string) andage(a number) under the next id, one more than the highest in use, and answers201with it andLocation: /cats/<id>. Without both, including a request with no JSON body at all, it answers400with{ "error": "name and age are required" }.- Replace the
GET /cats/:idroute withapp.route('/cats/:id'), declaring three handlers:GETanswers the cat,PATCHapplies the body's fields to it and answers it,DELETEremoves it and answers204with no body. Each answers404with{ "error": "Cat <id> not found" }for a cat that does not exist.
When it fails
Cannot destructure property 'name' of 'req.body' as it is undefined, and a 500: the parser is missing, mounted after the routes, or the request was not JSON. Mountexpress.json()first and destructure fromreq.body ?? {}.- "Create Milo" answers 200:
res.json()alone keeps the default status.res.status(201)first. - "Delete Milo" answers 200 with
OKor{}:res.send()orres.json()was used for the empty answer.res.sendStatus(204).
Remember
- Nothing reads the body until a parser does:
app.use(express.json()), before the routes. - In Express 5, an unparsed body is
undefined; usereq.body ?? {}. res.status(201).location(url).json(value)for a create,res.sendStatus(204)for a delete.app.route(path)declares a path once with a handler per method.
Stuck? Show a hint
app.use(express.json()) before the routes parses JSON bodies. In Express 5, req.body is undefined when no parser read the body, so destructure from req.body ?? {}. app.route('/cats/:id').get(…).patch(…).delete(…) chains the handlers. res.sendStatus(204) sends the status and nothing else.