Deploy a Full-Stack App on GCP with NGINX as a Load Balancing (API Gateway) and Reverse Proxy

Biswajit Nandi
13 min readOct 9, 2023

--

Deploying a Full-Stack App (Frontend, Backend, and Database) on GCP with NGINX as a Load Balancing(API Gateway) and Reverse Proxy

Hi everyone!
Welcome to our comprehensive guide on deploying a high-performance full-stack application using NGINX for load balancing and reverse proxying on the Google Cloud Platform (GCP). follow this step-by-step walkthrough to deploy your apps.

What do we want?

We create a VPC and two subnets on a private and a public in GCP. On the public subnet install nginx on a VM instance which has a public IP that is used for the access of our application and on the private subnet host our frontend, backend, and database there VM is no public IP so, it can not be accessed outside of this network or internet which is very secure. also configure the NGINX as a load balancer (API Gateway) for distributing load based on the URL and server.

Deploy a Full-Stack App on GCP with NGINX as a Load Balancing and Reverse Proxy Demo
Deploy a Full-Stack App on GCP with NGINX as a Load Balancing and Reverse Proxy Demo

📌 Steps Overview:

We have a few steps above the solution:

  1. Create a VPC and two subnets (public & private).
  2. Create a VM (Load Balancer) in the public Subnet, and install Nginx for load balancing or reverse proxy.
  3. Create a VM (frontend) in the private subnet for the frontend.
  4. Create 2 VMs (backend) in the private subnet for the backend 1 & 2.
  5. Create a VM (database) in the private subnet for the database.
  6. Access the URL and see the demo.

📘 Section 1:

Step 1: Create a ‘Virtual Private Cloud (VPC)’ and under this VPC create ‘two subnets’ (public subnet & private subnet).

Create a VPC with two subnets (public & subnet)
Create a VPC with two subnets (public & subnet)

1. Click the VPC network > VPC networks > “Create VPC network” button.
2. Enter a name for your VPC network “custom-vpc”.
3. Subnet creation mode > Custom.
4. From the subnet section enter the name of the subnet -1 ‘public-subnet’.
5. For Region > ‘us-central1’.
6. Specify the IP address range for the public subnet (10.10.1.0/24).

7. Similarly, create a private subnet click ‘Add subnet’, and enter the name of the subnet -2 ‘private-subnet’, for Region > ‘us-central1’ and specify the IP address range for the subnet (10.10.2.0/24).
8. From the Firewall rules select “custom -vpc-allow-icmp” for ping &
custom -vpc-allow-ssh’ for ssh connection.
9. Then click “Create” to create the VPC network.

📘Section 2:

Step 1: Create a VM instance (load-balancer) on the public subnet for load balancing or reverse proxy.

Create a Load Balancer VM
Create a Load Balancer VM instance

1. Click the Compute Engine > VM instances > ‘Create instance’ button to create a new VM instance.
2. In the Name field, enter a name for your instance “load-balancer”.
3. Choose the region ‘us-central1’ and a zone where you want to create the VM instance.
4. In the Machine type section, select the desired machine type for your instance.
5. For the Firewall click “Allow HTTP traffic”, it’s open port ‘80’ for HTTP traffic.
6. Now go to Advanced options > Networking > Network interfaces then select network “custom-vpc & Subnetwork “public-subnet” & External IPv4 address select ‘Ephemeral’.
7. Then click the “Create” button to create the VM instance.

Step 2: Connect to the ‘Load Balancer’ VM instance terminal and install ‘NGINX ’.

# Install nginx
sudo apt update
sudo apt install nginx

Step 3: Use the public IP of the ‘load balancer VM instance to check the nginx install successfully.

Nginx install successfully

📘Section 3:

Step 1: Create a VM instance (frontend) in the private subnet for the frontend that has no public IP.

We just follow the process from (Section 2 > Step 1) to create a VM instance (frontend) just change the VM instance name ‘frontend’ and don’t allow any Firewall here also External IPv4 address select ‘none’ from Network interfaces.

At this stage, our VM instance is in the private subnet and it has no public IP. so, now connect to the internet need to create a NAT Gateway.

Step 2: Create a public NAT Gateway for internet connectivity to the private network. if you don't know follow here. just remember that you select your VPC “custom-vpc”.

Step 3: Connect to the ‘frontend’ VM instance terminal and install ‘Node js’.

How to install Node js, For more details.

  1. Download and import the Node source GPG key.
sudo apt-get update
sudo apt-get install -y ca-certificates curl gnupg
sudo mkdir -p /etc/apt/keyrings
curl -fsSL https://deb.nodesource.com/gpgkey/nodesource-repo.gpg.key | sudo gpg --dearmor -o /etc/apt/keyrings/nodesource.gpg

2. Create a deb repository.

NODE_MAJOR=20
echo "deb [signed-by=/etc/apt/keyrings/nodesource.gpg] https://deb.nodesource.com/node_$NODE_MAJOR.x nodistro main" | sudo tee /etc/apt/sources.list.d/nodesource.list

3. Run update and install ‘node js’.

sudo apt-get update
sudo apt-get install nodejs -y

# Check Node Version
sudo node -v

Step 4: Install the React app.

# Install react
npx create-react-app my-app
cd my-app
npm start

Step 5: Now, create a firewall rule for the ‘frontend’ VM instance to open react application port ‘3000’.

Go to VPC NEtwork > Firewall rule > Create Firewall Rule

Name: react
Network: custom-vpc
Targets: Specified target tags
Target tags: react
Source filter: IPv4 ranges
Source IPv4 ranges: 0.0.0.0/0
Protocols and ports: Specified protocols and ports > TCP > 3000

Step 6: Now, put the react tag on the ‘frontend ’ VM instance.

Go to VM > Click the VM Name ‘frontend’ > Edit > Network tags > Save

Now, If we check from the ‘load balancer’ VM instance using Curl
(curl 10.10.2.2:3000) with frontend private IP. See some of the raw code which is react frontend.

React app Raw Code
React app Raw Code

Step 7: Now, configure the ‘load balancer’ VM instance to see the react application (frontend) from the ‘load balancer’ VM instance public IP.

  1. Go to the ‘load balancer’ VM instance terminal and run this.
sudo rm /etc/nginx/nginx.conf
sudo nano /etc/nginx/nginx.conf

2. Add this code to the ‘nginx.conf’ file.

events {
# empty placeholder
}

http {

server {
listen 80;

location / {
proxy_pass http://frontend;
}
}

upstream frontend {
server 10.10.2.2:3000; # Replace with your frontend VM IP
}
}

3. Now, test the code and reload the server.

# Test the nginx config file is right formate
sudo nginx -t

# Now restart the nginx server
sudo nginx -s reload

Step 8: Use the public IP of the ‘load balancer’ VM instance to check the react app load successfully.

React app load from the frontend VM Instance
React app load from the frontend VM Instance

📘Section 4:

Step 1: Now, create a VM instance in the private subnet for the ‘backend 1’.

We just follow the process from (Section 2 > Step 1) to create a VM instance (backend 1) just change the VM instance name to ‘backend 1’ and don’t allow any Firewall here also ‘External IPv4 address’ select ‘none’ from Network interfaces.

Step 2: Connect to the ‘backend 1 VM instance from the terminal and install ‘Node js’ from the following (Section 3 > Step 3) or see more details.

Step 3: Now, install the Express js server for the ‘backend 1’.

mkdir myapp

cd myapp

npm init

npm install express

Step 4: Create the ‘index.js’ file.

sudo nano index.js

Put this code on the ‘index.js’ file.

const express = require('express')
const app = express()
const port = 5000

app.get('/', (req, res) => {
res.send('backend -1!')
})

app.listen(port, () => {
console.log(`Example app listening on port ${port}`)
})

Step 5: Now, run the express js code.

node index.js

Step 6: Now, create a firewall rule for the VM instance ‘backend 1’ to open Express js server port ‘5000’.

Go to VPC NEtwork > Firewall rule > Create Firewall Rule

Name: backend
Network: custom-vpc
Targets: Specified target tags
Target tags: backend
Source filter: IPv4 ranges
Source IPv4 ranges: 0.0.0.0/0
Protocols and ports: Specified protocols and ports > TCP > 5000

Step 7: Now, put the ‘backend’ tag on the ‘backend-1’ VM instance.

Go to VM > Click the VM name ‘backend -1’ > Edit > Network tags > Save

Now, if we check from the ‘load blancher’ VM instance using Curl (curl 10.10.2.3:5000) using ‘backend 1’ private IP. See some of the responses from the ‘backend 1’ VM instance.

Responses from backend-1
Responses from backend-1

📘Section 5:

We follow the same process respectively from

Create VM instance : (Section 2 > Step 1),
Install Node js : (Section 3 > Step 3),
Install Express js: (Section 4 > Step 3)

for ‘backend 2’ but just change the ‘name ’of the VM instance to ‘backend 2’and the ‘index.js’ page to replace ‘backend-2! ’with ‘backend-1!’ because we can understand easily which server we use.

Also, now we skip (Section 4 > step 6) because we have already created a firewall rule for the VM instance ‘backend 2’ to open Express js server port ‘5000’.

Step 1: Just change the response to ‘backend-2!’ from (Section 4 > Step 4).

const express = require('express')
const app = express()
const port = 5000

app.get('/', (req, res) => {
res.send('backend-2!')
})

app.listen(port, () => {
console.log(`Example app listening on port ${port}`)
})

Step 2: Also add the firewall rule to the ‘backend 2’ VM instance from (Section 4 > Step 7).

Other Sections and Steps we do very carefully.

Now, if we check from the ‘load blancher’ VM instance using Curl
(curl 10.10.2.4:5000) using ‘backend 2 ’ private IP. See some of the responses from the ‘backend 2’ VM instance.

Responses from backend-2
Responses from backend-2

📘Section 6:

Step 1: Now, configure the ‘load balancer’ VM instance to see the backend application from the ‘load balancer’ VM instance public IP.

  1. Go to the ‘load balancer’ VM instance terminal.
  2. Open the ‘nginx.conf’ file.
sudo nano /etc/nginx/nginx.conf

3. Add this code to the ‘nginx.conf’ file in the ‘server ’ block.

location /backendapi/ {
rewrite ^/backendapi/(.*)$ /$1 break;
proxy_pass http://backend;
}

4. add this code to the ‘http’ block

upstream backend {
server 10.10.2.3:5000; // Replace with your backend-1 IP
server 10.10.2.4:5000; // Replace with your backend-2 IP
}

Now full code like this.

events {
# empty placeholder
}

http {
server {
listen 80;
location / {
proxy_pass http://frontend;
}
location /backendapi/ {
rewrite ^/backendapi/(.*)$ /$1 break;
proxy_pass http://backend;
}
}
upstream frontend {
server 10.10.2.2:3000; # Replace with your frontend IP
}
upstream backend {
server 10.10.2.3:5000; # Replace with your backend-1 IP
server 10.10.2.4:5000; # Replace with your backend-2 IP
}
}

Step 2: Now, test the code and reload the server.

# Test the nginx config file is right formate
sudo nginx -t

# Now restart the nginx server
sudo nginx -s reload
Nginx Server Test
Nginx Server Test

Step 3: Use the public IP of the ‘load balancer’ VM instance to check the backend Server responses successfully. it shows a one-time ‘backend 1!’ and a one-time ‘backend 2!’ because it performs with a round-robin algorithm. so, now we go to the URL like this ‘http://load_balancer_ip/backendapi/’.

  1. Responses from the backend 1.
Load Balancer Public IP & Responses from the backend 1 server
Load Balancer Public IP & Responses from the backend 1 server

2. Responses from the backend 2.

Load Balancer Public IP & Responses from the backend 2 server
Load Balancer Public IP & Responses from the backend 2 server

At that stage, we successfully load-balanced by Nginx.

📘Section 7:

Step 1: Create a VM instance (database) in the private subnet for the database that has no public IP.

We just follow the process from (Section 2 > Step 1) to create a VM instance (database) just change the VM instance name to ‘database and don’t allow any Firewall here also ‘External IPv4 address’ select ‘none’ from Network interfaces.

Step 2: Connect to the ‘database’ VM instance terminal and install the MongoDB server.

  1. Import the public key used by the package management system.
sudo apt update
curl -fsSL https://pgp.mongodb.com/server-7.0.asc | \
sudo gpg -o /usr/share/keyrings/mongodb-server-7.0.gpg \
--dearmor

2. Create the list file using the command appropriate for your version of Debian.

echo "deb [ signed-by=/usr/share/keyrings/mongodb-server-7.0.gpg ] http://repo.mongodb.org/apt/debian bullseye/mongodb-org/7.0 main" | sudo tee /etc/apt/sources.list.d/mongodb-org-7.0.list

3. Reload the local package database.

sudo apt-get update

4. Install the MongoDB packages.

sudo apt-get install -y mongodb-org

5. Start the MongoDB server.


# Enable MongoDB to start at system startup
sudo systemctl enable mongod

# Start MongoDB server
sudo service mongod start

# You can view the status
sudo service mongod status

6. Check MongoDB server is running.

Our MongoDB Server is Running

Step 3: Now, enable the remote connections on the database you need to edit the ‘mongod.conf’ file and add ‘0.0.0.0’ IP to the network interfaces.

  1. Open the ‘mongod.conf’ file.
sudo nano /etc/mongod.conf

2. Update the code on the ‘mongod.conf’ file like this. it’s for accessing databases from other VM instances.

bindIp: 0.0.0.0 

3. Restart MongoDB for the changes to take effect.

sudo systemctl restart mongod

Step 4: Now, create a firewall rule for the VM instance ‘database’ to open MongoDB port ‘27017’.

Go to VPC NEtwork > Firewall rule > Create Firewall Rule

Name: database
Network: custom-vpc
Targets: Specified target tags
Target tags: database
Source filter: IPv4 ranges
Source IPv4 ranges: 0.0.0.0/0
Protocols and ports: Specified protocols and ports > TCP > 27017

Step 5: Now, put the database tag on the ‘database ’ VM instance.

Go to VM > Click the VM Name ‘database’ > Edit > Network tags > Save

Step 6: Now, go to the backend -1 VM instance terminal, and if the server is running stop it with the ‘ctrl + c’ press from the keyboard.

Now, If we want to check that the database is open for the backend server, just use telnet from the backend.

telnet 10.10.2.5 27017 // Replace with your database VM instance IP

Check the database connection from the backend. connect to the database when we open port ‘27017 ’ from the ‘database’ VM instance.

Check DB connection from Backend
Check DB connection from the Backend

Then stop it with the ‘ctrl + c’ press from the keyboard.

Step 7: Now, we create a directory & create a node js app.

mkdir node-app-demo
cd node-app-demo
sudo nano package.json
  1. Put the code on the package.json file.
{
"name": "nodejs-demo-app-sort",
"version": "1.0.0",
"description": "",
"main": "index.js",
"scripts": {
"start": "node index"
},
"author": "",
"license": "ISC",
"dependencies": {
"axios": "^1.5.1",
"body-parser": "^1.20.2",
"express": "^4.18.2",
"mongodb": "^6.1.0"
}
}

2. Open the index.js file.

sudo nano index.js

3. Put the code on the ‘index.js’ file, and replace your load_balancer_ip with your ‘load balancer ’ VM instance public IP and mongodb_ip with your ‘MongoDB ’ VM instance IP.

const express = require('express');
const { MongoClient } = require('mongodb');
const bodyParser = require('body-parser')
const axios = require('axios');
const app = express();
const PORT = 5000;

const HOSTNAME = 'backend 1'
const LBIP = 'load_balancer_ip' // Replace with your load balancer server IP

// Replace the URL string with your mongodb connection string.
const mongoUrl= 'mongodb://mongodb_ip:27017/';


const client = new MongoClient(mongoUrl);
const db = client.db('mydatabase'); // Name of your database
const collection = db.collection('mycollection'); // Name of your collection

let dbConTest = '';
// Database connection function
async function connectToMongoDB() {
try {
await client.connect();
console.log('Connected to MongoDB Server');
dbConTest = 'Connected';
} catch (error) {
console.error('Error connecting to MongoDB Server:', error);
}
}

connectToMongoDB();

// Home page
app.get('/', (req, res) => {

const currentURL = req.protocol + '://' + LBIP + req.originalUrl + 'backendapi/';

if (dbConTest == 'Connected') {
res.send(`<h1>Host: ${HOSTNAME} </h1> <h3>Database connection success</h3>Browser for insert data <a href="${currentURL}insertData"; target="_blank" ><b>${currentURL}insertData</b></a><br>Browser for fatch data <a href="${currentURL}fetchData"; target="_blank" ><b>${currentURL}fetchData</b></a>`);
} else {
res.send('Error connecting to MongoDB')
}

});

app.use(bodyParser.json())

// Client site request with constant value
app.get('/insertData', (req, res) => {

const useInput = {
name: 'Biswajit Nandi',
email: 'nbiswajit94@gmail.com'
};

// Request to server from client & get response
axios.post('http://localhost:5000/insert', useInput)
.then(response => {
res.send(response.data)
})
.catch(error => {
console.error('Error inserting data:', error);
});
});

// Insert data to database, request from client
app.post('/insert', async (req, res) => {

const userdata = req.body;

try {
await collection.insertOne(userdata);
res.json({ host: HOSTNAME, Message: 'User inserted successfully', User: userdata });
} catch (error) {
res.status(500).json({ error: 'Could not insert user' });
}
});

// Get data from database
app.get('/fetchData', async (req, res) => {

try {
const hostinfo = { host: HOSTNAME };
const result = await collection.find().toArray();
const responseArray = [hostinfo, result];
res.send(responseArray);
} catch (error) {
console.error('Error fetching data:', error);
}
});

app.listen(PORT, () => {
console.log(`Server is running on port ${PORT}`);
});
  1. Now, install the packages.
npm install

2. Start the server.

npm start

We follow the same process for ‘backend-2’ from (Section 7 > Step 6) but just change on the ‘index.js’ page to replace backend-1 with backend-2. because we can understand easily which server we use, and replace your load_balancer_ip with your ‘load balancer’ VM instance public IP and mongodb_ip with your ‘MongoDB ’ instance IP.

Step 8: Now, access the URL for the demo. URL like this ‘http://load_balancer_ip/backendapi/’.

http://load_balancer_ip/backendapi
  1. Home page of the backend.
Backend 1
Backend 2

2. Insert data from the backend.

Insert data from the backend 1
Insert data from the backend 2

3. Fetch data from the backend.

Fetch data from the backend 1
Fetch data from the backend 2

🌟Congratulations🌟, We have successfully deployed a full-stack app (frontend, backend, and database) on GCP with an NGINX as a Load Balancing(API Gateway) and reverse proxy. which just have a public IP on the load balancer, other instances have a private IP in the private subnet.

#GCP #NGINX #FullStack #Deployment #LoadBalancing #ReverseProxy #CloudComputing

If you face any issues with these full-stack apps on GCP with NGINX as a Load Balancing (API Gateway) and Reverse Proxy feel free to contact me. I will try my best. Thank you

--

--

Biswajit Nandi

Hello! I'm a DevOps engineer with experience in AWS, GCP, Kubernetes, Docker, Terraform and full CI/CD pipelines.