/ Javascript

Simple NodeJs Reverse Proxy for a React App and its API Backend

How to integrate a React app with an API back-end without messing everything up. A simple implementation of a reverse proxy with NodeJs to serve a React App and offer an API gateway.

The easiest configuration for a simple React web app is to serve front-end resources from the same host and port as the back-end implementation.

This solution has several advantages:

  • almost removes the need of environment configuration on the front-end
  • no struggling with custom configurations
  • no need of repeated builds per environment
  • removes the need of CORS

And disadvantages:

  • using a same port implies at least two machines
  • or an awful copy of front-end artifacts in the back-end environment
  • or worse, a copy of front-end artifacts in the back-end ones

In such configuration, webpack-development-server offers a great solution to serve both front-end resources and a proxied access to an API back-end. Moreover, it comes pre-configured with Facebook's create-react-app generator. But what to do if we want to keep the same approach in production without adding more technology entropy?

So these are my objectives:

  • Complete independence of back-end and front-end artifacts and runtimes
  • Build Once Run Everywhere approach to ease Continuous Delivery and Integration
  • Easiest solution without introducing other languages, components or complex configurations

Next, I will describe how to implement a simple reverse proxy with NodeJs to serve the React app and offer an Api gateway. Basically, it consists in a single NodeJs script and a Docker image, configurable via environment variables.

The server.js script

The server.js file is the single node script that provides all the needed functions. The project layout I am considering is the one of Facebook's create-react-app builder. So the script expects the static resources to be in the "build" folder.

var express = require('express');
var compression = require('compression');
var httpProxy = require('http-proxy');
var API_HOST = process.env.API_HOST || 'localhost:8081'
var PORT = process.env.SERVER_PORT;

// Initialize
var app = express();
var apiProxy = httpProxy.createProxyServer();

// Serve static resources from 'build' folder
app.use(express.static('build'));

// Enable gzip response compression
app.use(compression());

// Proxy all the api requests
app.all('/api/*', function (req, res) {
  apiProxy.web(req, res, { target: 'http://' + API_HOST })
});

// Otherwise serve index.html
app.get('*', function (req, res) {
  res.sendfile("/build/index.html");
});

app.listen(PORT);

console.log('Running on port' + PORT + ' with API_HOST:'+API_HOST);

The Dockerfile

This is a simple Dockerfile that builds the React app and starts a NodeJs instance.

FROM node:9.2-alpine
ADD ./ /
RUN npm install && npm run build 
ENV API_HOST UNDEFINED_HOST
ENV SERVER_PORT 8080
ENV NODE_ENV production
EXPOSE 8080
ENTRYPOINT [ "sh", "-c", "node server.js" ]

The build steps are the following:

  1. move source code to the docker daemon
  2. install npm dependencies
  3. build React App

Configuration is provided by mean of environment variables that can be overwritten at runtime (e.g. with docker run --env <key>=<value>). The entry point simply executes the server.js script.

Final considerations

Although it may be not the best choice for large scale deployments, this very simple solution fits a lot of common use cases and provides great flexibility:

  • small amount of configuration
  • friendly environment for front-end devs
  • can be easily deployed in a container based CI pipeline
  • can be extended and customized as needed with very little efforts (e.g error messages)
  • integration for session-based authentication systems can be added with other dependencies, allowing the underlying REST API to be stateless.

Thanks for reading 🤓