Routes and responses
Send one clear response
Choose status, headers, content type, and body deliberately and return after completing the response.
A handler has exactly three ways to finish: send a response, call next(), or throw. Pick one per branch, and make the code return right after. Most double-response bugs come from a branch that sends and then keeps going.
Status, headers, body, in that order
Creating a note should say what happened and where the new thing lives:
notesRouter.post('/', express.json(), (req, res) => {
const note = notes.create(req.body)
res.status(201).location(`/notes/${note.id}`).json(note)
})
201 Created tells the client the request made something. The Location header says where. res.json() sets Content-Type: application/json and serializes the body. Three decisions, one line, all visible.
Deleting a note has nothing to return, so don’t invent a body:
notesRouter.delete('/:id', (req, res) => {
notes.remove(req.params.id)
res.status(204).end()
})
204 No Content with an empty body is the honest answer. A { "ok": true } body adds nothing the status doesn’t already say.
Return after you send
Here is a handler with a bug that the happy path never shows:
notesRouter.get('/:id', (req, res) => {
const note = notes.find(req.params.id)
if (!note) {
res.status(404).json({ error: 'Note not found' })
}
res.json(note)
})
Ask for a note that exists and it works. Ask for a missing one and Node throws:
Error [ERR_HTTP_HEADERS_SENT]: Cannot set headers after they are sent to the client
The 404 went out, then res.json(null) tried to send a second response. The fix is one word:
if (!note) {
return res.status(404).json({ error: 'Note not found' })
}
I write return res... on every early exit, even when nothing follows yet. Code gets added below later, and the return is already there.
Every branch terminates once
For a handler with several outcomes, I list them and check that each ends the request exactly one way:
- valid input, note created:
201with JSON body - invalid input:
400with the validation errors - note not found:
404 - unexpected failure: throw, and let the error middleware answer
The last one matters. When the database call rejects, don’t catch it and send a 500 yourself. Throw, or let the rejection propagate, and the error middleware we build later handles it in one place.
Express 5 tightened this
Two old shortcuts are gone. res.send(404) no longer sets a status, it would send the string. Use res.sendStatus(404) or res.status(404).send(...). And res.json(body, status) was removed in favor of chaining res.status(201).json(body). res.status() also now throws if you pass anything other than an integer between 100 and 999, so a typo like res.status('201') fails loudly.
Try this: write a supertest case for each branch above and assert on status, Content-Type, and body. When the four tests pass, you know each branch terminates once.
Lesson completed