How Express routes a request
A browser asks for GET /users/42. Express walks it down one list, in the order you wrote your code, until a route sends a reply or the list runs out.
Express hands the ticket to its router
Node's server gets the request and calls your app like a plain function. The app preps the ticket, then hands it to its router with a back-up slip.
app.listen passes the app to http.createServer, so each request runs app.handle(req, res). It sets X-Powered-By: Express and points req and res at Express's prototypes, which is where res.send comes from. Then it calls this.router.handle(req, res, done), where done is finalhandler(req, res).
next() shows the ticket to each station in order
Your setup code built the line. Each app.use and app.get added one station to the end. The router starts at the first station and moves right.
const app = express()
app.use(logger) // stack[0]
app.get('/users/:id', show) // stack[1]
app.listen(3000)
The line is router.stack, an array of Layer objects. next() takes stack[idx++] and calls layer.match(path). stack[0] has path / and end: false, so it fits every URL. It has no route, so the router calls logger(req, res, next). When logger calls next(), the walk goes on at stack[1].
A real ticket goes to one station. Here every station that fits can work on it, then pass it on.
Express has no route table. It walks one list, in the order you wrote it.
The next station reads its sign and fills in the blank
The sign on stack[1] says /users/:id. The ticket says /users/42. They fit, so the blank :id gets 42. Then the station checks its menu for GET.
The Layer built its matcher with path-to-regexp, using end: true and trailing: true. A hit returns { path: '/users/42', params: { id: '42' } }. Next, route._handlesMethod('GET') finds methods.get, and a HEAD request also counts as GET. The router copies the match to req.params. Then processParams runs any app.param('id', …) callback, once per value in a request.
| Path | Fits the sign? |
|---|---|
/users/42 | yes id: '42' |
/users/42/ | yes Strict routing is off. |
/USERS/42 | yes Matching ignores case. |
/users/42/posts | no end: true needs the whole path. |
The station's cook makes the dish
Inside the station is a short line of its own: one cook per handler you passed to app.get. The station gives the ticket to the first cook for GET. The cook sends the dish out, and the walk ends.
The station's own function calls route.dispatch(req, res, next). route.stack holds one Layer per handler, tagged layer.method = 'get'. dispatch picks the next layer whose method is empty or matches, and calls show(req, res, next). res.send('user 42') writes the reply, and nobody calls next again. A cook can skip the rest of the station with next('route').
If nobody answers, the back-up slip goes out
Say show calls next() instead, or the ticket said /teams/7. The walk reaches the end of the line. The router sends the back-up slip from step 1.
When next() runs past the last layer, it calls done(layerError). With no error, finalhandler answers 404 with the body Cannot GET /users/42. With an error, the router skips routes. It calls only layers whose function takes four arguments.
| Handler does | What happens |
|---|---|
res.send(…) | sent The walk stops here. |
next() | 404 finalhandler sends Cannot GET. |
next(err) | error Four-argument handlers run. If none replies, finalhandler uses err.status or 500. |
A 404 is not an error. It is the end of the line with no reply.
Before you rely on it
- catchAn error handler with three parameters never sees errors.
handleErrorskips any function whosefn.lengthis not 4. Write(err, req, res, next), even if you never callnext. - ask nextDoes Express look routes up in a table? No. Each request walks
router.stackfrom index 0. A route near the end is tested after every layer before it. - ask nextDo async handlers need try/catch? Not to reach your error handler. When a handler's promise rejects,
handleRequestcallsnext(err)for you. - ask nextHow does
app.use('/api', router)see/users/42?trimPrefixcuts/apifromreq.urland moves it toreq.baseUrl. The nextnext()puts it back. - simplifiedLeaves out
OPTIONSauto-replies,mergeParams, regex paths, and the guard that yields withsetImmediateafter 100 synchronous layers. - sourcesRead at Express 5.3.0 and router 2.2.0, the version Express's
package.jsonasks for (^2.2.0): express.js, application.js, router index.js, layer.js and route.js. Also finalhandler 2.1.1 and path-to-regexp 8.4.2.