A route takes any number of handlers, run in order as long as each calls next(). Arrays work too, so app.get(path, [authenticate, authorize], handler) is valid and nests to any depth. This is how you compose per-route middleware without touching the global stack: the expensive check runs only where it is needed.
next('route') is the escape hatch. Rather than advancing to the next handler in this route, it abandons the route and resumes the search at the following layer — useful for an optional fast path that falls back to a plain one. app.route(path) returns a Route whose method calls chain, keeping the path string in one place.
const requireKey = (req, res, next) => (req.query.key === 'secret' ? next() : next('route'));
const loadBook = (req, res, next) => { req.book = { id: req.params.id }; next(); };
app.get('/books/:id', requireKey, loadBook, (req, res) =>
res.json({ via: 'private', book: req.book }));
app.get('/books/:id', (req, res) => res.json({ via: 'public', route: req.route.path }));
app.route('/books/:id')
.put((req, res) => res.json({ method: 'PUT' }))
.delete((req, res) => res.status(204).end());200 GET /books/7?key=secret {"via":"private","book":{"id":"7"}}
200 GET /books/7 {"via":"public","route":"/books/:id"}
200 PUT /books/7 {"method":"PUT"}
204 DELETE /books/7req.route inside a handler is the Route instance that matched, and req.route.path is the unexpanded pattern — log that instead of req.path and your metrics group by endpoint rather than exploding into one label per book id.
Anything else passed to next() is treated as an error and jumps straight to the error-handling middleware, skipping every remaining route. In Express 5 24,430 a rejected promise from an async handler does the same thing automatically, which Async Middleware covers in detail.