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 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
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:
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:
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:
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
:
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:
if (helpers.isMultipartRequest(req)) {
reqAsBuffer = true;
reqBodyEncoding = null;
}
The will pass requests with files straight through to our internal APIs.
The following:
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:
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.