[Fixed] 404 fallback route in Express

Issue

I am utterly confused by the seemingly bad documentation of Express. I want to archive a very simple thing: Return a custom 404 for every unmatched route in express. At first, this seems very straight forward:

app.get('/oembed', oembed()); // a valid route
app.get('/health', health()); // another one
app.get('*', notFound()); // catch everything else and return a 404

However, when a valid url has been opened (like /oembed) express continues to work through the routes and ends up calling notFound() as well. I still see my /oembed response but I get an error in the console saying notFound() is trying to set headers (404) when the body has already been sent.

I tried to implement a middleware that catches errors like this

function notFound() {
  return (err, req, res, next) => {
    console.log(err);
    res.sendStatus(404);
    next(err);
  };
}

and added it with app.use(notFound());, but this won’t even get called. I find it hard to find anything on this on the internet (e.g. that is not outdated or wrong) and the official documentation does not seem to have anything specific for this very standard use case. I’m kind of stuck here and I don’t know why.

Solution

Taking your implementation of oembed:

export default () => function oembed(req, res, next) {
  const response = loadResponseByQuery(req.query);

  response.onLoad()
    .then(() => response.syncWithS3())
    .then(() => response.setHeaders(res)) // sets headers
    .then(() => response.sendBody(res))   // sends body
    .then(() => next())                   // next()

    .catch(() => response.sendError(res))
    .catch(() => next());
};

It’s calling next after sending the response body, which means that it will propagate the request to any following (matching) route handlers, including the notFound() handler.

With Express, next is generally only used to pass along a request if the current handler doesn’t respond to it, or if doesn’t know how to, or wants to, deal with it.

Leave a Reply

(*) Required, Your email will not be published