Search code examples
reactjsexpresshttp-proxy

Difference between app.use and app.get *in proxying*


I'm hoping to better understand the difference between express's app.get() and app.use().

I understand that app.use applies to all HTTP verbs.

I have also read that "app.use() adds middleware rather than a route"

I'd like to understand why this fact causes this behaviour...

I have an express API server that needs to proxy a React development web server.

This means that all routes that are not API routes have to be proxied.

When I proxy the routes like this, it works:

var proxy = require('express-http-proxy');

module.exports = function set_react_catchall_routes(app) { 
    /* Final route to send anything else to react server. */
    app.get('*', proxy('localhost:3000'));
    app.post('*', proxy('localhost:3000'));
}

But when I do this it does not work:

    app.use('*', proxy('localhost:3000'));

Specifically, the "index" page is proxied and served up, with content like this:

 <body>
    <div id="root"></div>
    <script type="text/javascript" src="/static/js/bundle.js"></script>
 </body>

and the client requests the javascript react bundle, but then "nothing happens".

I'm reasonably sure that there aren't "other" HTTP requests involved when it does work (other than GET and POST) because none are logged.

So what would be the difference?


Solution

  • Try putting this logging at the top, it should help to clarify what's going on:

    app.use(function(req, res, next) {
        // req.path will be '/static/js/bundle.js'
        console.log('path1: ' + req.path);
    
        next();
    });
    
    app.use('*', function(req, res, next) {
        // req.path will be '/'
        console.log('path2: ' + req.path);
    
        next();
    });
    
    app.all('*', function(req, res, next) {
        // req.path will be '/static/js/bundle.js'
        console.log('path3: ' + req.path);
    
        next();
    });
    

    When you use app.use it will strip off the matching section of req.path. If you don't specify a path (logging section 1) it won't strip anything off. Similarly section 3 is using app.all (app.get, etc. all work the same way) which doesn't change req.path either. It's section 2 that's the kicker.

    To understand why this happens consider this example:

    var router = express.Router();
    
    router.get('/profile', ...);
    
    app.use('/user', router);
    

    When a request comes in for /user/profile the app.use will strip off the /user part of the path. As far as router is concerned the path is just /profile.

    To quote the docs, http://expressjs.com/en/4x/api.html#req.path

    When called from a middleware, the mount point is not included in req.path.

    The path for a call to app.use is a bit like a 'starts with' and anything that matches is thrown away. For * that matches everything so it throws away everything.

    If you take a quick dive into the source code for express-http-proxy you'll see that it uses req.path to determine the path of the proxy request. If you just use app.use without a path it should work fine.

    There are some other request properties that are similarly relevant to understanding app.use:

    • req.url is similar to req.path but with the query string included. Just like req.path it will have the section matching the mountpath removed by app.use. Note that the Express Request inherits the url property from Node's http.IncomingMessage so it isn't explicitly listed in the Express documentation.
    • req.originalUrl starts off the same as req.url but it will not be changed by app.use.
    • req.baseUrl is used to store the section of the path that has been removed by app.use.

    See the documentation for req.originalUrl for more details on all three of these properties.