The Response Object
Express's res object wraps Node's raw ServerResponse with helpers that make sending replies a one-liner: res.json(), res.send(), res.status(), res.redirect(), and more. They infer content types, serialize data, and chain fluently. This page covers them all — and the one rule that governs every response: send exactly one.
The core senders
Method | Sends |
|---|---|
| JSON — sets |
| String/Buffer/object — infers |
| Sets status code (chainable, sends nothing) |
| Sets status + sends its standard text |
| Redirect (302 by default) |
| Stream a file with correct headers |
| Render a template to HTML |
| End with no body (e.g. 204) |
json and send
res.json({ id: 1, name: 'Ada' }) // Content-Type: application/json
res.send('<h1>Hello</h1>') // Content-Type: text/html
res.send(Buffer.from([1, 2, 3])) // Content-Type: application/octet-stream
res.send({ ok: true }) // object → JSON (same as res.json)Chaining status with the body
res.status(201).json({ id: 42 }) // 201 Created + JSON
res.status(404).json({ error: 'Not found' })
res.status(204).end() // no body
res.sendStatus(403) // → 403 + body 'Forbidden'The cardinal rule: one response per request
// ✗ Bug — both run, second send crashes
app.get('/x', (req, res) => {
if (!ok) res.status(400).json({ error: 'bad' }) // no return!
res.json(data) // runs anyway → throws
})
// ✓ Return after responding
app.get('/x', (req, res) => {
if (!ok) return res.status(400).json({ error: 'bad' })
res.json(data)
})Redirects
res.redirect('/login') // 302 (temporary) by default
res.redirect(301, '/new-home') // 301 permanent
res.redirect('https://example.com') // absolute URL
res.redirect('back') // back to the Referer (or '/')Setting headers, cookies, and content type
res.set('X-Custom', 'value') // one header
res.set({ 'X-A': '1', 'X-B': '2' }) // several
res.type('application/json') // shortcut for Content-Type
// Cookies (Express helper — no manual Set-Cookie string):
res.cookie('sessionId', 'abc', {
httpOnly: true, secure: true, sameSite: 'strict', maxAge: 3_600_000,
})
res.clearCookie('sessionId')Sending files and downloads
import { join } from 'node:path'
// Stream a file inline (sets Content-Type from the extension):
res.sendFile(join(import.meta.dirname, 'public', 'report.pdf'))
// Prompt a download with a filename:
res.download(join(import.meta.dirname, 'files', 'data.csv'), 'export.csv')Quick reference
Goal | Call |
|---|---|
Send JSON |
|
Send with status |
|
Empty success |
|
Redirect |
|
Set a cookie |
|
Stream a file |
|
Force download |
|