Appearance
Req, res & JSON
Most FE ↔ Express traffic is JSON in, JSON out. Learn the helpers and status codes; ignore the rest until you need files or streams.
Reading the request
ts
app.post('/api/notes', (req, res) => {
const { title, body } = req.body // needs express.json()
const userAgent = req.get('user-agent')
const auth = req.headers.authorization
const { id } = req.params
const page = req.query.page
// …
})| Piece | Where |
|---|---|
| JSON body | req.body after express.json() |
| URL params | req.params |
| Query | req.query |
| Headers | req.headers or req.get('Authorization') |
Bodies are not auto-validated. Garbage in stays garbage until you check (we'll use Zod later).
Writing the response
ts
res.status(201).json({ id: 1, title: 'hi' })
res.status(204).end()
res.status(400).json({ error: 'title required' })
res.status(401).json({ error: 'unauthorized' })
res.status(404).json({ error: 'not found' })
res.status(500).json({ error: 'server error' })Comments for the code above:
json()sets Content-Type and stringifiesend()for empty bodies (204 No Content)- Chain
statusthenjson- order is readable that way
Status codes FE people already half-know
| Code | Meaning in APIs |
|---|---|
| 200 | OK - here's data |
| 201 | Created - usually POST success |
| 204 | Success, no body |
| 400 | Your fault - validation |
| 401 | Not authenticated (who are you?) |
| 403 | Authenticated but not allowed (AuthZ) |
| 404 | Missing resource |
| 409 | Conflict (duplicate email…) |
| 422 | Sometimes used for validation (style choice) |
| 500 | Our fault |
Your SPA should branch on these the same way it already does with if (!res.ok).
Headers you'll care about
ts
res.set('X-Request-Id', requestId)
res.json({ ok: true })CORS headers are usually set by the cors package - don't hand-roll Access-Control-* unless you enjoy pain.
Don't mix HTML and JSON by accident
ts
// SPA talks JSON
res.json({ error: 'nope' })
// not:
res.send('<h1>nope</h1>') // surprise HTML for fetch().json()If you're building a JSON API for a Vite app, stay JSON - even for errors.
Async handlers (gotcha)
ts
app.get('/api/notes', async (req, res, next) => {
try {
const notes = await db.listNotes()
res.json({ notes })
} catch (err) {
next(err) // forward to error middleware
}
})Unhandled promise rejections in Express 4 don't always hit error middleware automatically. Either wrap, or use a tiny helper / Express 5. We'll standardize this in Error handling.
Next: centralizing those next(err) paths so every route doesn't invent its own 500 shape.
Best,
-- ł.