NodeJSExpress Routing

Express Routing

Routing in Express is mapping an incoming request — its HTTP method and URL path — to the handler that should respond. Express makes this declarative: app.get('/users', handler). This page covers the method functions, path patterns, multiple handlers per route, and the ordering rules that decide which route wins when several could match.

Method routing functions

JS
app.get('/users', handler)       // GET
app.post('/users', handler)      // POST
app.put('/users/:id', handler)   // PUT
app.patch('/users/:id', handler) // PATCH
app.delete('/users/:id', handler)// DELETE

app.all('/users', handler)       // ANY method
app.use(handler)                 // every method + path (middleware)
One function per HTTP method, mirroring the verbs
Each [HTTP method](/nodejs/http-methods) has a matching `app.METHOD` function. `app.all` matches any method for a path; `app.use` matches everything and is how middleware is mounted. Choosing the right method function is part of designing a clean REST API.
route() — group handlers for one path

When several methods share a path, app.route() avoids repeating it:

JS
app.route('/books')
  .get((req, res) => res.json(allBooks()))
  .post((req, res) => res.status(201).json(create(req.body)))

app.route('/books/:id')
  .get((req, res) => res.json(findBook(req.params.id)))
  .put((req, res) => res.json(replace(req.params.id, req.body)))
  .delete((req, res) => res.sendStatus(204))
Path patterns

Pattern

Matches

/about

Exactly /about

/users/:id

/users/42req.params.id = "42"

/users/:id/posts/:postId

Multiple named params

/files/*splat

Wildcard — captures the rest of the path

/:id (regex constrained)

Use a RegExp route for digit-only, etc.

Express 5 changed wildcard and optional-parameter syntax
In Express 4, the bare `*` wildcard and optional params like `/users/:id?` worked directly. Express 5 (built on a newer path-to-regexp) **requires named wildcards** — `/*splat` instead of `*` — and changed optional-parameter and regex handling. If a route pattern from an older tutorial throws a path-parsing error, this version bump is almost always why. Check the docs for the exact v5 syntax.
The cardinal rule: order matters
Routes are matched top-to-bottom — the FIRST match wins
Express checks routes in the order you register them and stops at the first that matches. So a general route placed before a specific one will shadow it: register `/users/:id` before `/users/new`, and a request for `/users/new` matches `:id` with `id="new"` — the specific route never runs. **Always put specific/static routes before dynamic/catch-all ones.**

JS
// ✗ Wrong order — '/users/new' is captured by :id
app.get('/users/:id', getUser)
app.get('/users/new', showNewForm)   // unreachable!

// ✓ Specific first, dynamic after
app.get('/users/new', showNewForm)
app.get('/users/:id', getUser)
Multiple handlers per route

A route can take several handlers; each calls next() to pass to the next. This is how per-route middleware (auth, validation) attaches:

JS
function requireAuth(req, res, next) {
  if (!req.headers.authorization) {
    return res.status(401).json({ error: 'Unauthorized' })
  }
  next()                               // pass control to the next handler
}

function validate(req, res, next) {
  if (!req.body.title) return res.status(422).json({ error: 'title required' })
  next()
}

// Handlers run left to right; any can short-circuit by responding:
app.post('/posts', requireAuth, validate, (req, res) => {
  res.status(201).json(create(req.body))
})
A handler either responds or calls `next()` — not both
Each function in the chain should do exactly one of: send a response (ending the request), or call `next()` to continue. Doing neither leaves the request hanging; doing both triggers "Cannot set headers after they are sent." The `return res.status(401)...` pattern ensures you stop after responding.
The 404 and error fallbacks

Routes registered last act as catch-alls. A middleware with no path catches unmatched requests (404); a function with four arguments is the error handler:

JS
// ...all your routes above...

// 404 — nothing matched (place AFTER all routes):
app.use((req, res) => {
  res.status(404).json({ error: 'Not Found' })
})

// Error handler — 4 args (err, req, res, next), place LAST:
app.use((err, req, res, next) => {
  console.error(err)
  res.status(err.status || 500).json({ error: 'Internal Server Error' })
})
The error handler MUST have four parameters
Express distinguishes error-handling middleware purely by **arity**: a function with four parameters `(err, req, res, next)` is treated as an error handler; three or fewer is normal middleware. Omit the `next` parameter (even if unused) and Express won't route errors to it. Both the 404 and error handlers must be registered *after* all routes, or they'd intercept everything.
Key takeaways
  • Use app.METHOD(path, ...handlers); group shared paths with app.route().

  • Register specific/static routes before dynamic (:param) and wildcard ones.

  • Each handler responds or calls next() — never both, never neither.

  • Place the 404 catch-all and the 4-arg error handler last.

  • Mind Express 5 path syntax (named wildcards) if following older guides.

Next
Capture dynamic segments from the URL: [Route Parameters](/nodejs/route-parameters).