Build and Deploy a Web-App with React, Flask, NGINX, PostgreSQL, Docker and Google Kubernetes Engine: Part 1

Abhishek Chakraborty
8 min readApr 5, 2020

This is Part 1 of a two-part article series. This part covers the development of our application. We will dockerize and deploy our application next in Part 2.

OVERVIEW: In this two-article series, we will build a simple Addition web-application with a React-Nginx Web-server and Flask-WSGI Application-server. We will then Dockerize and deploy the application on Google Kubernetes Engine. If you are new to any of the tech used here, I link helpful resources throughout the article. Note: As this article serves as a simple helpful reference for people intending to build more complex applications with high-traffic use cases, our application and deployment infrastructure example is most certainly overkill for our simple application.

UPDATE: As developers we are always learning, and recently I also learned some slightly smarter techniques for making fetch calls to the backend REST API and for configuring Nginx. For an example of these specific updates, check out my to-do-list mock app code and the new corresponding article!

In our application, users will enter two numbers to add and click “submit” and see their answer. Furthermore, their query will be saved to a database (DB) and they can view their queries history by clicking “Show history”. Our front-end will be built with React and will be served with an NGINX server. This will make up our web-server. Our backend will be built using Flask and will use WSGIserver to serve. This will make-up our application-server. Our web-server will make HTTP requests to our application-server via a reverse-proxy with NGINX. Using a reverse proxy enables many benefits, including additional security for users and a more efficient flow of traffic.

We begin with our Flask application. Our flask application’s main responsibility will be to accept HTTP requests (POST & GET) from our web-server. These requests will handle inserting a calculation into our DB (POST) and fetching our calculations history from our DB (GET). As majority of our application’s actions involve interacting with our DB, it makes sense to first set up our DB and the code for interacting with it. For our DB, we will use Google Cloud Platform’s (GCP) Cloud SQL service. Note: You will need to create a GCP account for this service (and also for our later deployment on its Kubernetes Engine service). GCP provides users with a few hundred dollars in credits upon account activation, which may or may not be enough to cover application costs depending on how fast you complete and remove services.

Once your account is active, create a project and name it. Next, create a PostgreSQL (PSQL) instance. A PSQL DB specifically is not mandatory. I chose it as I am most familiar with PSQL. Upon creating your instance, head over to Users and add a new user with username and password (remember these credentials).

Similarly, head to Databases and create a new DB with a relevant name. Also, note down the Public IP Address of your instance found in Overview. Create a file named config.ini with these credentials so they are accessible internally for connecting to your DB (remember do not push this to a public repo).

[history_database]
user = <username>
password = <password>
dbname = addition-app
host = <public-ip-address>

Next, we code out our python functions for interacting with our DB. First however, let’s organize our workspace. I am using this folder structure:

📂 addition-app
📂 flask_app (folder)
|_app.py
|_storage.py
📁 react-app (folder)
|_config.ini
|_requirements.txt
|_run.py

Now, we are ready to begin coding. You can find all the code for this entire project on my GitHub:

For connecting to and executing queries for interacting with our DB, we start in storage.py. There are several modules for executing SQL queries, I am using SQLAlchemy. I also use a configparser module to read my config.ini credentials file so I can connect to my DB. We create a series of functions to interact with our DB including create_history_table, insert_calculation, get_calculations and delete_calculation. For each of these functions, we execute SQL queries using our SQLAlchemy engine.

Now we move on to coding our application server’s HTTP endpoints in app.py. We are of course using the Flask framework. In case you are new to Flask, this article series is great. We will have two main routes: to insert a calculation into the DB and to fetch calculations from DB. In each route we also pass in a list of HTTP methods which our routes will accept requests from. For instance, our ‘/data’ route will only accept ‘GET’ request methods.

As Flask is a development server, we won’t run our application using Flask (since we are aiming to deploy) and instead use WSGIServer and run our application on port 8000 as specified in run.py.

Now we are ready to test our flask-application, but before that I will set up a local virtualenv which basically will make maintaining dependencies and Python versions easy. Equivalent to this, we must ensure that we keep our requirements.txt up-to-date with modules we install such as Flask and SQLAlchemy. Note, some module implementations, like our use of SQLAlchemy, require additional modules to be installed, so for our instance psycopg2. Thus, it is important to include these modules within requirements.txt as well. We use these commands to initiate, activate and install requirements on our virtualenv:

python3.7 -m venv addition-app-venv
source addition-app-venv/bin/activate
pip install -r requirements.txt
python run.py

For most application-servers, testing is done by sending HTTP requests using something like Postman. As our server interacts with a DB, TablePlus is also an excellent tool for viewing entries in your DB as you test. For the brevity of this article, I will not cover these parts, but highly recommend that you try these tools. Upon running your server at localhost:8000, we see the message “My Addition App” on the page!

We now code our main web server page with React. In our main web server, we will have two fetch calls to our Flask application server, one to insert a new calculation into our DB and one to collect all calculations. Our HTTP fetch calls will be made to our web server IP with the addition of either /api/insert_nums (POST) or /api/data (GET). In this way, we will proxy our requests to the appropriate application-server HTTP endpoint via NGINX. Before we begin coding, we need to get our NGINX server ready on our local setting to serve our static code.

NGINX setup on your local machine can be tedious. The main portion of the NGINX setup is your config file. In this file, NGINX looks for a path to HTML/JS script (static content) to serve. It also searches for which IP to serve content to and additional elements related to CORS and proxying. Our static files will be produced by our react-app in a build folder and when working locally, each time we update our react code, we have to update our build folder in the route specified in our NGINX config. This guide is pretty good for showing a sample NGINX setup. Here is our NGINX config:

Our server is set to listen on port 8080, and looks for files to serve in root /usr/local/etc/nginx/build/. This the file path on macOS, file paths for NGINX will differ on other OS. In index, we define our index file. Next, we define our first route with location /. Here, using try_files we specify that on a client request of localhost:8080/, NGINX should route to index.html in the current root. Another good example of NGINX routing is given here.

Now we handle our next route with location /api. Here we specify config for NGINX to follow on a client request of localhost:8080/api/<fetch_request>. As defined here, client_max_body_size defines the maximum size of the request body as 10MB.

add_header 'Access-Control-Allow-Origin' http://localhost:8080

Next, we add a response header allowing our web-server’s IP to gather a response from its fetch request to the application-server. Finally, we reroute to http://localhost:8000/<fetch_request> via proxy_pass. Again, as highlighted in this guide, to get NGINX to serve our config file, we need to create a directory for our servers (which I named servers) and need to update our local nginx.conf file by adding “include servers/*.conf” at the end. We now have our NGINX server ready to serve our static content! Remember to replace your react-build file when there are new changes and use these commands:

nginx <- start nginx
nginx -s stop <- stop nginx
Note: you might need to add sudo before these commands

We are now ready to write our frontend code and fetch calls using React! If you are new to React, there’s a lot of great resources out there, including this YouTube series I found helpful. We will use create-react-app project to set up the base of our application.

npx create-react-app react-app

For brevity, I will not cover all the React code details, these can be found on my GitHub. Instead let’s focus on our two fetch calls:

Our first fetch call of insertCalculation is triggered after the user clicks submit. It makes an HTTP request which is proxied to the Application server’s insert_nums HTTP endpoint. Notice the additional arguments passed into our fetch call, these arguments are essential to ensuring our request works as we expect. We use method ‘POST’ as we are sending data with the request and also expect data with the response. We use mode ‘CORS’ and set header Content-Type as application/json. Finally, we pass in a JSON format of calculations (our inputted operands plus answer) in body which is received by our application-server using Flask’s request.

Our second fetch call of getHistory is triggered after the user clicks the “Show history” button. It similar to our insertCalculation request, with the main exception that we are using a ‘GET’ method as we are not passing any data with our request. Notice that in each of these requests we made sure our method complied with the list of methods set by our Flask routes.

cd react-app/
npm run build (produce build folder)
\\ at this point replace build folder for NGINX to serve
nginx(start NGINX)
source addition-app-venv/bin/activate (make sure virtualenv active)
pip install -r requirements.txt (make sure requirements installed)
python run.py (control C to stop application server)
nginx -s stop (stop NGINX)

With our React code complete, we are ready to produce build files for our react-app and set-up and start our NGINX and Application servers to test our app locally. Above is a list of helpful commands you’ll find yourself using repeatedly. Upon going to localhost:8080, we find our application running!

In conclusion, while our Addition App example is obviously absurdly simple, the tools and development methodologies applied here are very powerful and enable you to build a very wide array of complex applications. IMPORTANT: As we are making use of the Cloud SQL service, as long as you leave the service running, it will incur charges. So if you do not intend to continue to deploying your app anytime soon, it might be beneficial to shut down your Cloud SQL service. Now we move on to containerizing and deploying our application in Part 2!

--

--