Mounting and 404s

Mounting, Base Paths and Not-Found Routes

app.use('/api/v1', router) mounts a router at a prefix. Express 24,430 strips that prefix before the router runs, which is why every path inside the router is written relative to the mount point. The original request is still available: req.baseUrl holds the matched prefix, req.url the remainder the router sees, and req.originalUrl the untouched path plus query string.

Mount paths, and a not-found route at the bottom of the stackJavaScript
app.use('/api/v1', where);          // where.get('/where', ...)
app.use('/api/v1/books', books);
app.use((req, res) => res.status(404).json({ error: 'Not Found', path: req.originalUrl }));
Output
200 /api/v1/where?debug=1
    {"baseUrl":"/api/v1","url":"/where?debug=1","originalUrl":"/api/v1/where?debug=1"}
404 /api/v2/books
    {"error":"Not Found","path":"/api/v2/books"}

Log req.originalUrl, never req.url: inside a mounted router the latter has lost its prefix, and a log line reading GET /7 tells you nothing. A mount path is a prefix match, so /api/v1 also catches /api/v1/anything/deeper — but the prefix breaks on segment boundaries, so a router mounted at /api receives /api, /api/ and /api/books, never /apiary.

The not-found handler is a path-less app.use registered after every route: nothing above it responded, so it matches by default. Put it last — above your error-handling middleware, below everything else — and it also catches paths that exist under a method you did not implement. A wildcard route works, but only in the braced form: app.use('/{*splat}', notFound) matches the root path while app.use('/*splat', notFound) leaks a request for / straight past it. Without any such layer, Express falls back to finalhandler, which sends an HTML page reading Cannot GET /books/7 — the right status, but the wrong body for an API whose clients expect a consistent error shape.

Two routing options change matching, and both are per-router. strict distinguishes /books from /books/; caseSensitive distinguishes /Books from /books. app.set('strict routing', true) affects only routes registered directly on the app: a router built with express.Router() keeps its own defaults and must be constructed as express.Router({ strict: true, caseSensitive: true }). With the app setting on, /Books/ returns 404 while a mounted router still answers /Tags/ and /tags with 200.