Express ‘cannot GET’ will cause an http headers sent error in app.use

So this might not be an issue with express’ internal workings, as I did post this up on stackoverflow initially thinking that this issue was purely arising from how I am approaching using express, but after experimenting with multiple setups I’m not so sure what’s what. Currently I have a setup for hot reloading as follows:

// server.js
const app = express();

app.use(function (req, res, next) {
    require('./server/index')(req, res, next);
    require('./server/foo')(req, res, next); // another url page
});

Each require file inside app.use follows the following example template:

const router = express.Router();

router.get('whatever endpoint' you want, (req, res) => {
  console.log('whatever endpoint you want');
  res.send('whatever message you want');
});

module.exports = router;

My understanding is that app.use with just a callback will apply to every possible route. And if a user navigates to a route that doesn’t exist, say ‘/bar’ for example, express’ cannot GET/ should be displayed at whatever the incorrect endpoint was.

What actually happens however, is that express crashes with the ERR_HTTP_HEADERS_SENT error. So of course that’s bad, so to get around that, I would just apply my own catch-all inside the above app.use at the end of the function as follows:

//server.js
app.use(function (req, res, next) {
    require('./server/index')(req, res, next);
    require('./server/foo')(req, res, next); 
    require('./server/catch-all')(req, res, next);
});

//catch-all.js
router.get('*', (req, res) => {    
  console.log('catch all');
  if(!res.headersSent){ // this is necessary to prevent the ERR_HTTP_HEADERS_SENT error
    res.redirect('/');
  }
})

This fixes all of the issues, but I’ve noticed that the catch-all console.log is always called twice; once for when the rest of the require statements have been called and it has not found a match, hence using the catch-all endpoint. The second is for when the redirect to the main page happens.

If my understanding is correct, since app.use() is being called for every single request, this causes the double call effect when invoked by the redirect handler? If so, if we omit the catch-all, and go back to what was originally done at the top of this post, why does express crash with the ERR_HTTP_HEADERS_SENT error? Is it again because of the app.use being called for every route, including routes that aren’t defined and being handled by cannot GET?

If this is the case, and this is the crux of what I’m trying to get at, shouldn’t express internally recognise that the cannot get / error handler has already been called? It seems like it’s attempting to send two responses that both say cannot GET whatever page the error is on, hence the ERR_HTTP_HEADERS_SENT error.

The alternative is splitting the above into two separate app.use calls, that only handle specific routes:

app.use('/', require('./server/index'));
app.use('/foo', require('./server/foo'));

However, if you have numerous routes, having loads of app.use calls littering your code becomes repetitive, and your docs state both options as being valid. Is it just a case of it needing to be done this way by design or am I perhaps missing another possible option?

Author: Fantashit

1 thought on “Express ‘cannot GET’ will cause an http headers sent error in app.use

  1. That makes a lot of sense @wesleytodd . Yea, I see that too digging in: in one middleware, there are two require and immediately invoke their returned function, which means if the first one wrote a response, the second one will also try to write a response, triggering the headers already sent error. Of course if the hotreloading is doing the require overwrite like you think, the app.use(require('./server/index'), require('./server/foo')); wouldn’t work as observed. Perhaps the best way around this is to write a little function to encapsulate the requireing:

    app.use(dynamic('./server/index'), dynamic('./server/foo'));
    
    function dynamic(lib) {
      return function (req, res, next) {
        return require(lib).apply(this, arguments)
      }
    }

    That would then allow you to write out what looks like standard Express syntax but get it to require the lib path on every request 👍

Comments are closed.