Files
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)
Routes and bodies
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.
Press Run tests to start the app. Its log appears here.Graded endpoints
express.json() put the body in req.body; 201, the stored cat, and a Location
Milo is stored
A body without both fields is the client's mistake: 400
express.json() only reads JSON, so req.body stays undefined: still a 400, not a crash
GET on the route declared with app.route()
The body's fields are applied to the stored cat
The change was made to the stored cat
404 from the PATCH handler
res.sendStatus(204): the status, and no body
404 from the DELETE handler
404 from the GET handler
The collection works as before after a delete: the new cat is stored and answered