PPL 2020 — Document Builder: React + Express + Docker: Is It a Recipe for Success?

Ivana Irene Thomas
pepeel
Published in
5 min readApr 13, 2020

These last couple of years, Javascript has been mentioned as the most popular language for early-stage and professional developers. Reasons are: it’s versatile, it can be used to program both client-side and server-side programming, the learning curve is moderately easy, and there is a lot of community support. Its popularity then increases the demand for “Javascript developers” all over the industry.

Amid the rise of Javascript’s popularity, some of its frameworks and libraries also become insanely popular. One of the most popular and widely used Javascript UI libraries is React. For server-side programming, Express became the most popular and widely chosen framework. This duo is widely used by the industry. When our team is made known of the requirement of our PPL project is using React as frontend and Node.js as the backend, we had mixed feelings about it. None of us had extensive knowledge nor experience about server-side programming using Javascript. At the same time though, we know that there are a wide range and extensive community support for it, thus made us a little bit relieved.

In this post, I’d like to explore and share our experiences in building a web app using React, Node.js and package them together with Docker, how convenient or inconvenient it is, and whether it’d be suitable for your needs.

A brief look inside our app’s architecture

Diagram of the tech stack and architecture by Firman Hadi Prayoga

Our app is built in a monorepo with three main components:

  1. Frontend Service (Serving user interface) using React.js
  2. Backend Service (Serving REST API) using Express + Sequelize ORM
  3. Database using PostgreSQL

These three main components are packaged and served with Docker. Frontend and Backend services as one service, and the Database as another.

Even though the backend and frontend services live in one repository with the same programming language, Javascript, we created a distinction by serving them separately. The frontend service does not know the logic and implementation of the backend service and the backend service does not know how the frontend service works. The backend serves REST API and the frontend calls them. Here’s what the code structure looks like in the codebase:

Code structure in the codebase

Our frontend service logic lives in the client folder and the backend service lives in the server folder. We created a separate libs folder as a home for shared logic between the client and the server.

One of the use cases for shared logic is the logic of extracting the content of a .docx document. It is a quite complex logic that is needed both by the frontend (to show the user the content of the .docx) and by the backend (to validate the content of a .docx). One of the advantages of using backend and frontend frameworks in the same language is this: we can have a shared logic written once for both.

However, what happens a lot in the industry, especially in large companies is they want to have a separation of concern and responsibility. Instead of having other teams/business units depending on the code that one team writes, for example, it is better to rewrite them and hold the responsibility for any breaking changes themselves. Therefore, although this shared concept introduces modularity into the codebase, political responsibility for other teams might arise later. In conclusion, even though it looks tempting, modular, and clean, this might not be the way you want to go if you work in a big codebase with a lot of teams contributing to it.

How we containerize them together

Before we look at how we organize this, let’s look at how we build the frontend and backend services separately.

The build script in our package.json looks like this:

Part of our package.json

The command server:build:server builds the content of src/server using babel and place the build result in /dist-server/server. The command server:build:libs builds the content of src/libs (the shared logic) and place the build result in dist/server/libs. When we run the command server:build, we run both server:build:server and server:build:libs .

The command client:build produce production build using the built-inreact-scripts build. This script will produce the production build in /build folder and we then move them to /dist-client .

This production-ready builds will then be used when building our docker image.

There three main steps that we do during our docker image build process:

  1. Build the backend and frontend (using the command described above)
  2. Install the dependencies
  3. Setting NODE_PATH to /dist-server (backend production builds)

Afterward, on production, we run the docker-compose up command with the following configuration:

In this configuration we basically do two things:

  1. Serve our PostgreSQL db using postgres:12.1-alpine image
  2. Serve our backend and frontend service with the image that we build earlier

Conclusions

After passing two sprints, we haven’t really encountered any major problems caused by our current architecture and technology stack. Having our front end and back end in the same language and in the same repository has its own set of advantages and drawbacks.

Some of the advantages are:

  1. We can have shared logic. Javascript is a pretty robust and versatile language and we do not have to define functions and logic differently when writing code for the client and server.
  2. We can change the context between writing backend and frontend code quite easily. It becomes less intimidating.
  3. The build process and build engine are pretty uniform. This means more shared dependencies and less space used altogether.

Some of the drawbacks are:

  1. Sharing one repository between the backend and frontend does not work well most of the time when the app can become very big.
  2. Due to its asynchronous nature, callback hell can happen on server-side programming

All in all, I think this React + Express architecture is suitable for small projects and early-stage startups as developers can hit the ground running. However, if you’re looking for scalability and longer-term development and maintenance, a lot more thought should be put before you build your application this way.

Feedback on this writing is very much appreciated. I hope it was useful!

--

--