Skip to content

Routing ​

Method + path → handler. Routers let you group /api/auth/* like feature folders in a SPA.

Methods you already use from the browser ​

ts
app.get('/api/notes', listNotes)
app.post('/api/notes', createNote)
app.get('/api/notes/:id', getNote)
app.patch('/api/notes/:id', updateNote)
app.delete('/api/notes/:id', deleteNote)

Same verbs as fetch(url, { method: 'PATCH' }). If the method doesn't match, Express keeps looking - and eventually 404s if nothing fits.

Path params ​

ts
app.get('/api/notes/:id', (req, res) => {
  const { id } = req.params
  res.json({ id })
})

/api/notes/42 → req.params.id === '42' (string!). Parse to number yourself when you need math.

FE map: React Router /notes/:id → useParams(). Same idea, server-side.

Query strings ​

ts
// GET /api/notes?q=ada&limit=10
app.get('/api/notes', (req, res) => {
  const q = String(req.query.q ?? '')
  const limit = Number(req.query.limit ?? 20)
  res.json({ q, limit })
})

Express Router (please use this) ​

ts
// src/routes/notes.ts
import { Router } from 'express'

export const notesRouter = Router()

notesRouter.get('/', (_req, res) => {
  res.json({ notes: [] })
})

notesRouter.get('/:id', (req, res) => {
  res.json({ id: req.params.id })
})
ts
// src/app.ts
import { notesRouter } from './routes/notes.js'

app.use('/api/notes', notesRouter)

Comments for the code above:

  • Paths on the router are relative to the mount - GET / means /api/notes
  • Keeps app.ts thin
  • Feels like splitting Vue Router into feature route modules

Order matters ​

Express matches top-down:

ts
notesRouter.get('/archived', …)  // ok
notesRouter.get('/:id', …)       // would catch "archived" if listed first

Same footgun as file-based routers: put static segments before param routes.

404 for unknown API paths ​

ts
app.use('/api', (_req, res) => {
  res.status(404).json({ error: 'not found' })
})

Mount this after real routers. Otherwise it steals traffic. We'll refine errors in Error handling.

What about app.all / app.use for routes? ​

  • app.use('/x', fn) runs for every method under /x (middleware style)
  • Prefer explicit get / post for HTTP resources so intent stays obvious

Next: the onion in the middle - middleware.

Best,

-- ł.

Notes-style Express tutorial for front-end folks