Build Your Own Reverse Proxy With Express
Tags: node, javascript, webdev, tutorial
Published: 2020-01-12 23:28:09

**Subscribe to my email list now at http://jauyeung.net/subscribe/**

**Follow me on Twitter at https://twitter.com/AuMayeung**

**Many more articles at https://medium.com/@hohanga**

If you use microservices architecture for your distributed system, there is a need for an API gateway. One good way to build an API gateway is to use a reverse proxy. [Node.js](https://nodejs.org/en/) is good for a reverse proxy because it’s fast and has lots of libraries to make a reverse proxy.

Express is the most popular framework for building web apps. It’s also very good for building a reverse proxy because there are add-ons to Express that can do the routing for the reverse proxy.

The design will be very simple. It will only redirect traffic to our different back-end apps according to the URL of the request. Also, it has to be able to pass header data, files, and cookies into our services in addition to the request payload.

To do all this, we first scaffold our app by running the Express application generator by following the instructions at [https://expressjs.com/en/starter/generator.html](https://expressjs.com/en/starter/generator.html)

We have to run `npx express-generator` to generate the code that we need.

I suggest using `nodemon` to run our app in a development environment so that it restarts whenever our code changes.

Next, we have to install some packages to do the reverse proxy function and to enable CORS so that front end apps can use the reverse proxy. To do this, run `npm i express-http-proxy glob node-env-file cookie-parser babel-register body-parser` .

`express-http-proxy` is the HTTP reverse proxy library. `cors` is an add-on for Express that enables CORS. `cookie-parser` is an add-on allows Express to parse cookies. `babel-register` allows us to use the latest JavaScript features. `node-env-file` allows us to use `.env` file for storing environment variables. `body-parser` will be used to check for multipart requests. Multipart requests are requests that have files. The requests with files will not go through `body-parser` before redirecting.

Now we are ready to write code. We make a new file called `helper.js` in the `helpers` folder that we create.

In there, we add:

```javascript
module.exports = {  
    isMultipartRequest: (req) => {  
        let contentTypeHeader = req.headers['content-type'];  
        return contentTypeHeader && contentTypeHeader.indexOf('multipart') > -1;  
    }  
}
```

This function checks for multipart requests, which are requests that are sent in as form data. It can include text or files.

In `app.js` , we write:

```javascript
require("babel-register");  
let express = require('express');  
let cors = require('cors')  
let config = require('./config/config');  
let env = require('node-env-file');  
let helpers = require('./app/helpers/helpers');  
let bodyParser = require('body-parser');  
env(__dirname + '/.env');
let app = express();

app.use(cors({  
  credentials: true,  
  origin: true  
}));

const bodyParserJsonMiddleware = function () {  
  return function (req, res, next) {  
    if (helpers.isMultipartRequest(req)) {  
      return next();  
    }  
    return bodyParser.json()(req, res, next);  
  };  
};

app.use(bodyParserJsonMiddleware());

app.all('*', (req, res, next) => {  
  let origin = req.get('origin');  
  res.header('Access-Control-Allow-Origin', origin);  
  res.header("Access-Control-Allow-Headers", "X-Requested-With");  
  res.header('Access-Control-Allow-Headers', 'Content-Type');  
  next();  
});

module.exports = require('./config/express')(app, config);

app.listen(config.port, () => {  
  console.log('Express server listening on port ' + config.port);  
});
```

The code works by passing on server-side cookies and allows multipart requests to not pass through `body-parser` since it is not JSON.

Also, we allow CORS in the block below:

```javascript
app.all('*', (req, res, next) => {  
  let origin = req.get('origin');  
  res.header('Access-Control-Allow-Origin', origin);  
  res.header("Access-Control-Allow-Headers", "X-Requested-With");  
  res.header('Access-Control-Allow-Headers', 'Content-Type');  
  next();  
});
```

It allows requests from all origins to be accepted. By default, only requests from the same host as the back end are accepted since it is insecure to allow requests from other hosts. However, if we allow mobile and standalone front-end web apps to make requests through our reverse proxy, we have to allow all origins. It gets the origin from the header and allows the request from that origin to proceed.

Then we add the code for proxying requests in `controllers/home.js`:

```javascript
const express = require('express');  
let proxy = require("express-http-proxy");  
let helpers = require('../helpers/helpers');
let loginAppRoutes =[  
  '/login*',  
  '/loginms',  
  '/register',  
  '/resetPassword',  
  '/getActivationKey*',  
  '/activateAccount',  
  '/logout',  
  '/reports',  
  '/'  
]

let uploadRoutes = [  
  '/uploadlogo*',  
]

module.exports = (app) => {  
  const proxyMiddleware = () => {  
    return (req, res, next) => {  
      let reqAsBuffer = false;  
      let reqBodyEncoding = true;  
      let contentTypeHeader = req.headers['content-type'];  
      if (helpers.isMultipartRequest(req)) {  
        reqAsBuffer = true;  
        reqBodyEncoding = null;  
      }  
      return proxy(process.env.UPLOAD_URL, {  
        reqAsBuffer: reqAsBuffer,  
        reqBodyEncoding: reqBodyEncoding,  
        parseReqBody: false,  
        proxyReqOptDecorator: (proxyReq) => {  
          return proxyReq;  
        }, 

        proxyReqPathResolver: (req) => {  
          return `${process.env.UPLOAD_APP_URL}/${req.baseUrl}${req.url.slice(1)}`;  
        }, 

        userResDecorator: (rsp, data, req, res) => {  
          res.set('Access-Control-Allow-Origin', req.headers.origin);  
          return data.toString('utf-8');  
        }  
      })(req, res, next);  
    };  
  } 

  uploadRoutes.forEach(r => {  
    app.use(r, proxyMiddleware());  
  }) 

  loginAppRoutes.forEach(r => {  
    app.use(r, proxy(process.env.LOGIN_URL, {  
      proxyReqOptDecorator: (proxyReq) => {  
        return proxyReq;  
      },

      proxyReqPathResolver: (req) => {  
        return `${process.env.LOGIN_URL}/${req.baseUrl}${req.url.slice(1)}`;  
      }, 

      userResDecorator: (rsp, data, req, res) => {  
        res.set('Access-Control-Allow-Origin', req.headers.origin);  
        return data.toString('utf-8');  
      }  
    }));  
  })  
};
```

We check for multipart form requests with:

```javascript
if (helpers.isMultipartRequest(req)) {  
  reqAsBuffer = true;  
  reqBodyEncoding = null;  
}
```

The will pass requests with files straight through to our internal APIs.

The following:

```javascript
proxyReqPathResolver: (req) => {  
  return `${process.env.LOGIN_URL}/${req.baseUrl}${req.url.slice(1)}`;  
},
```

is where the actual redirect URL from the proxy to internal API is set. Note that `parseReqBody` is `false` , so that multipart form requests are not parsed as JSON.

The `process.env` variables are set in the `.env` file.

In the `.env` file, we have:

```javascript
LOGIN_URL='http://urlForYourLoginApp'  
UPLOAD_URL='http://anotherUrlForYourUploadApp'
```

With reverse proxy add-on, Express is one of the best choices for building a reverse proxy in Node.