Search code examples
node.jsapiexpressmiddleware

Expressjs router based on Content-Type header


For routing, I'd like my middleware to pass the request the routes defined in a /html folder to server HTML(ejs), and if header Content-Type is application/json, use the routes defined in the /api folder.

But I don't want to have to define that in every route. So I'm not looking for middleware that defines some req.api property that I can check on in every route

app.get('/', function(req, res) {
    if(req.api_call) {
        // serve api
    } else {
        // serve html
    }
});

But I'd like something like this:

// HTML folder
app.get('/', function(req, res) {
    res.send('hi');
});

// API folder
app.get('/', function(req, res) {
    res.json({message: 'hi'});
});

Is this possible and if so, how can I do this?

I'd like it to work something like this:

app.use(checkApiCall, apiRouter);
app.use(checkHTMLCall, htmlRouter);

Solution

  • You can insert as the first middleware in the Express chain, a middleware handler that checks the request type and then modifies the req.url into a pseudo URL by adding a prefix path to it. This modification will then force that request to go to only a specific router (a router set up to handle that specific URL prefix). I've verified this works in Express with the following code:

    var express = require('express');
    var app = express();
    app.listen(80);
    
    var routerAPI = express.Router();
    var routerHTML = express.Router();
    
    app.use(function(req, res, next) {
        // check for some condition related to incoming request type and
        // decide how to modify the URL into a pseudo-URL that your routers
        // will handle
        if (checkAPICall(req)) {
            req.url = "/api" + req.url;
        } else if (checkHTMLCall(req)) {
            req.url = "/html" + req.url;
        }
        next();
    });
    
    app.use("/api", routerAPI);
    app.use("/html", routerHTML);
    
    // this router gets hit if checkAPICall() added `/api` to the front
    // of the path
    routerAPI.get("/", function(req, res) {
        res.json({status: "ok"});
    });
    
    // this router gets hit if checkHTMLCall() added `/api` to the front
    // of the path
    routerHTML.get("/", function(req, res) {
        res.end("status ok");
    });
    

    Note: I did not fill in the code for checkAPICall() or checkHTMLCall() because you were not completely specific about how you wanted those to work. I mocked them up in my own test server to see that the concept works. I assume you can provide the appropriate code for those functions or substitute your own if statement.

    Prior Answer

    I just verified that you can change req.url in Express middleware so if you have some middleware that modifies the req.url, it will then affect the routing of that request.

    // middleware that modifies req.url into a pseudo-URL based on 
    // the incoming request type so express routing for the pseudo-URLs
    // can be used to distinguish requests made to the same path 
    // but with a different request type
    app.use(function(req, res, next) {
        // check for some condition related to incoming request type and
        // decide how to modify the URL into a pseudo-URL that your routers
        // will handle
        if (checkAPICall(req)) {
            req.url = "/api" + req.url;
        } else if (checkHTMLCall(req)) {
            req.url = "/html" + req.url;
        }
        next();
    });
    
    // this will get requests sent to "/" with our request type that checkAPICall() looks for
    app.get("/api/", function(req, res) {
        res.json({status: "ok"});
    });
    
    // this will get requests sent to "/" with our request type that checkHTMLCall() looks for
    app.get("/html/", function(req, res) {
        res.json({status: "ok"});
    });
    

    Older Answer

    I was able to successfully put a request callback in front of express like this and see that it was succesfully modifying the incoming URL to then affect express routing like this:

    var express = require('express');
    var app = express();
    var http = require('http');
    
    var server = http.createServer(function(req, res) {
        // test modifying the URL before Express sees it
        // this could be extended to examine the request type and modify the URL accordingly
        req.url = "/api" + req.url;
        return app.apply(this, arguments);
    });
    
    server.listen(80);
    
    app.get("/api/", function(req, res) {
        res.json({status: "ok"});
    });
    
    app.get("/html/", function(req, res) {
        res.end("status ok");
    });
    

    This example (which I tested) just hardwires adding "/api" onto the front of the URL, but you could check the incoming request type yourself and then make the URL modification as appropriate. I have not yet explored whether this could be done entirely within Express.

    In this example, when I requested "/", I was given the JSON.