Skip to main content

Command Palette

Search for a command to run...

Deploying a 3-Tier Application (React, Node.js, MongoDB) with NGINX Reverse Proxy

Updated
16 min read
Deploying a 3-Tier Application (React, Node.js, MongoDB) with NGINX Reverse Proxy

1. Introduction — What Is This Project?

This project is a production-style full-stack application designed to demonstrate how real-world software systems are architected, structured, deployed, and secured.

Instead of building a small demo project, this system demonstrates an INDUSTRY-LEVEL ARCHITECTURE PATTERN used in real companies.

System Architecture Overview

Frontend → Backend → Database
with
NGINX → Reverse Proxy Controller


Technologies Used

Layer

Technology

Role

Frontend

React

User Interface

Backend

Node.js + Express

Logic Engine

Database

MongoDB

Data Storage

Web Server

NGINX

Traffic Router

This architecture pattern is called:

3-Tier Architecture

It is the FOUNDATION OF MODERN WEB APPLICATION DEVELOPMENT.


2. What Is a 3-Tier Architecture?

  • A 3-tier architecture separates responsibilities into independent logical layers.

Layer

Responsibility

Description

Presentation

UI

What users see

Application

Logic

How system thinks

Data

Storage

Where data lives


Why Companies Use This Architecture

Because it provides:

  • Scalability → Each layer can scale independently

  • Security → Database is hidden from public access

  • Maintainability → Code is organized logically

  • Flexibility → Layers can be replaced independently

  • Team Productivity → Frontend & backend teams work separately


3. System Architecture Visualization

User Browser
     ↓
NGINX (Entry Gate)
     ↓
React Frontend (UI Layer)
     ↓
Node Backend (Logic Layer)
     ↓
MongoDB (Data Layer)

Critical Rule Must Remember

NGINX never communicates directly with the database. All database access must go through the backend application layer.

Why?

  • Because database must always be protected behind application logic.

Security Principle:

Database should never be publicly accessible.


4. Repository Structure

DevOps-3tier-MIDDLEWARE
│
├── docker-compose.yml
├── README.md
│
├── backend
│   ├── Dockerfile
│   ├── package.json
│   ├── server.js
│   ├── config/db.js
│   ├── middleware/authMiddleware.js
│   ├── models/User.js
│   └── routes/auth.js
│
└── frontend
    ├── Dockerfile
    ├── package.json
    ├── public/index.html
    └── src
        ├── api/axios.js
        ├── components
        ├── pages
        ├── App.jsx
        ├── index.js
        └── index.css
  • Every folder exists for a SINGLE RESPONSIBILITY PURPOSE.

  • This is how enterprise repositories are designed.


5. Backend Layer — The Brain of Application

 backend
   ├── Dockerfile
   ├── package.json
   ├── server.js
   ├── config/db.js
   ├── middleware/authMiddleware.js
   ├── models/User.js
   └── routes/auth.js

Location: /backend

This layer is responsible for:

  • processing requests

  • validating users

  • securing routes

  • communicating with database

  • returning responses


5.1 server.js — System Startup Engine

This file is the FIRST FILE EXECUTED when backend starts.

Responsibilities:

  • start server

  • connect database

  • load middleware

  • register routes

Execution flow:

connectDB()
load middleware
load routes
start server

Concept:
server.js = application engine ignition.


5.2 config/db.js — Database Connector

This file establishes connection between backend and MongoDB.

mongoose.connect(MONGO_URI)

Why separate this file?

Because CONFIGURATION MUST BE ISOLATED FROM LOGIC.

Professional engineering principle:

Configuration should always be modular.


5.3 models/User.js — Database Blueprint

Defines structure of user data stored inside MongoDB.

Fields:

  • Name

  • Email

  • Password

Meaning:

Database will only store data that matches this structure.


5.4 routes/auth.js — Business Logic Controller

Defines API endpoints responsible for authentication.

Routes:

POST /register
POST /login

Responsibilities:

  • validate input

  • hash password

  • verify credentials

  • generate JWT token

  • send response

This file contains ACTUAL APPLICATION LOGIC.


5.5 middleware/authMiddleware.js — Security Layer

This acts as a PROTECTION FILTER.

Checks:

  • token exists?

  • token valid?

  • token expired?

If valid → allow request
If invalid → block request

Analogy:

Middleware = Security checkpoint before entry.


5.6 package.json — Backend Dependency Manager

This file defines:

  • libraries used

  • scripts

  • project configuration

Key libraries:

Library

Purpose

express

server engine

mongoose

database connection

bcryptjs

password hashing

jsonwebtoken

authentication

cors

cross-origin permission


6. Frontend Layer — User Interface System

 frontend
    ├── Dockerfile
    ├── package.json
    ├── public/index.html
    └── src
        ├── api/axios.js
        ├── components
        ├── pages
        ├── App.jsx
        ├── index.js
        └── index.css

Location: /frontend

This is the VISIBLE PART of application.

It handles:

  • UI rendering

  • navigation

  • API requests

  • token storage


6.1 public/index.html — Root HTML Container

Contains:

<div id="root"></div>

React injects entire application into this element.


6.2 src/index.js — React Entry Point

Mounts application into browser.

render(<App />)

Meaning: Load main component.


6.3 App.jsx — Navigation Controller

Handles routing between pages.

URL

Page

/

Home

/login

Login

/register

Signup

/dashboard

Dashboard

/courses

Courses

Concept:

App.jsx = traffic controller of UI navigation.


6.4 api/axios.js — API Communication Engine

This file is one of the most important files in the frontend because it controls how the frontend communicates with the backend server.

You should tell students:

This file acts as the central communication gateway between frontend and backend.

Purpose of axios.js

This file is responsible for three major things:

  1. Defining backend API base URL

  2. Automatically attaching authentication token

  3. Standardizing all API requests

Instead of writing backend URL in every component, we define it once here.


Base URL Configuration

Inside axios.js:

baseURL: process.env.REACT_APP_API_URL
  • The value comes from environment file:
frontend/.env
REACT_APP_API_URL=/api

What This Means Internally

All frontend API calls automatically start with:

/api

So if a component calls:

/auth/login

Actual request sent becomes:

/api/auth/login

Why This Is Very Important

Because NGINX is configured to route requests based on path.

Routing rule:

Request Path

Destination

/api/*

Backend

everything else

Frontend

So:

/api/auth/login → backend
/dashboard → frontend
  • That means axios baseURL and NGINX routing must match.

  • This is a critical architecture dependency.

  • All requests are authenticated automatically.

axios.js is the communication backbone of frontend.


Pages Folder — Application Screens

Each file represents one screen:

Home → landing page
Login → authentication page
Register → signup page
Dashboard → private page
Courses → content page


7. Authentication Flow Step-by-Step

  1. User enters credentials

  2. Login.jsx sends request

  3. Backend verifies credentials

  4. Backend generates token

  5. Token returned

  6. Frontend stores token

  7. Future requests automatically authenticated


8. Full Request Flow — Real Example

Request:

POST /api/auth/login

Execution Flow:

  1. User clicks login

  2. React sends request

  3. axios formats request

  4. NGINX receives request

  5. NGINX detects /api path

  6. NGINX forwards to backend

  7. Backend validates user

  8. MongoDB queried

  9. Backend returns token

  10. Frontend stores token

  11. User redirected


9. Three-Tier Application Deployment & NGINX Configuration

  • This section provides a step-by-step guide on running the project, detailing the purpose of each step and the role NGINX plays in the architecture.

  • It's not just about installation; it's about understanding how a real production system operates.


STEP 0 — Create EC2 Instance — Steps

  1. Open AWS Console

  2. Go to EC2 Dashboard

  3. Click Launch Instance

  4. Choose Ubuntu Server

  5. Select t2.medium

  6. Click Launch

STEP 1 — Install Base Tools (System Preparation Layer)

Before running any application, the server must have required runtime tools.

Command:

sudo apt update
sudo apt install -y git curl nginx nodejs npm

What each tool does

Tool

Purpose

git

download project

curl

download packages

nginx

traffic router

nodejs

backend runtime

npm

install libraries

Verify installation:

node -v
npm -v
nginx -v
git --version

STEP 2 — Install MongoDB (Database Layer First)

Database must start before backend because backend depends on database.

Add key:

curl -fsSL https://pgp.mongodb.com/server-6.0.asc | sudo gpg -o /usr/share/keyrings/mongodb-server-6.0.gpg --dearmor

Add repository:

echo "deb [ arch=amd64 signed-by=/usr/share/keyrings/mongodb-server-6.0.gpg ] https://repo.mongodb.org/apt/ubuntu jammy/mongodb-org/6.0 multiverse" | sudo tee /etc/apt/sources.list.d/mongodb-org-6.0.list

Install:

sudo apt update
sudo apt install -y mongodb-org

Start service:

sudo systemctl start mongod
sudo systemctl enable mongod
sudo systemctl status mongod

You must see:

active (running)

Always start dependencies first.

Dependency Order:

Database → Backend → Frontend → NGINX


STEP 3 — Clone Repository (Application Code Layer)

Download project source code.

git clone -b feature https://github.com/KandlaguntaVenkataSivaNiranjanReddy/DevOps-3tier-MIDDLEWARE.git
cd DevOps-3tier-MIDDLEWARE

What Does -b feature Mean?

  • Normally, Git clones the default branch (main/master).

  • But here we are specifically telling Git: “Do not clone main branch, clone the feature branch.”

  • So -b feature = Clone the feature branch.

cd DevOps-3tier-MIDDLEWARE

cd means change directory.

After cloning, we move inside the project folder because:

  • All backend and frontend code exists inside this directory

  • The server cannot run the application without entering the project folder.


STEP 4 — Start Backend (Logic Layer First)

Backend must run before frontend because frontend calls backend APIs.

cd backend
npm install

Create environment file:

vi .env

Add:

MONGO_URI=mongodb://localhost:27017/kkfunda
JWT_SECRET=secret

Start server:

npm start

Expected output:

MongoDB Connected
Backend running on 5000
  • Backend successfully connected to database and is ready to process requests.

  • Leave this running.

Why We Use This in .env

MONGO_URI=mongodb://localhost:27017/kkfunda
JWT_SECRET=secret
  • These are environment variables.

  • Environment variables are used to store configuration values outside the source code.

  • This is a professional best practice.

  • Break it down:

mongodb://      → database protocol
localhost       → database host
27017           → MongoDB default port
kkfunda         → database name

So this tells backend:

Connect to MongoDB running on this machine, on port 27017, and use database named "kkfunda".


Why It Is Needed

In config/db.js, we have:

mongoose.connect(process.env.MONGO_URI)
  • This means: Backend reads database connection string from environment variable.

  • Without this backend does not know:

  • where database is

  • which port to use

  • which database name to connect

  • So backend cannot connect.


Why Not Hardcode It?

Bad practice:

mongoose.connect("mongodb://localhost:27017/kkfunda")

Why bad?

Because in production:

  • database may be on another server

  • database URL may change

  • cloud MongoDB may be used

Using environment variable allows:

Development:

mongodb://localhost:27017/kkfunda

Production:

mongodb+srv://cluster.mongodb.net/prodDB

Same code → different config.

Professional principle:

Never hardcode configuration inside application code.


STEP 5 — Start Frontend (UI Layer)

  • Open new terminal.
cd frontend
npm install
  • Create env file:
vi .env
REACT_APP_API_URL=/api
  • Start frontend:
npm start

Runs on: localhost:3000

  • Frontend is just a UI server.

  • It cannot work unless backend is running.

Why We Create .env in Frontend?

  • Inside frontend/.env we write:
REACT_APP_API_URL=/api
  • REACT_APP_API_URL=/api Meaning

  • When the frontend makes a backend call

  • /api is automatically added in front of the request

In One Simple Line

  • Frontend needs to know the backend address, Where to send backend requests
    so we define /api inside the .env file,
    and NGINX forwards that /api traffic to the backend (port 5000).

  • Frontend cannot guess backend location automatically.


What Does This Actually Do?

In axios.js:

baseURL: process.env.REACT_APP_API_URL

So if .env has:

REACT_APP_API_URL=/api

Then every API request automatically starts with:

/api

Example:

Frontend code:

axios.post("/auth/login")

Becomes:

/api/auth/login
  • In the frontend, I configured Axios baseURL using environment variables.
    By setting REACT_APP_API_URL=/api, all API calls automatically prefix with /api.

  • NGINX then routes /api traffic to the backend service running on port 5000.
    This avoids exposing backend ports publicly and follows production best practices.


STEP 6 — Configure NGINX (FINAL ENTRY LAYER)

  • This is the FINAL STEP because NGINX routes traffic to services that are already running.

  • Create config:

sudo vi /etc/nginx/sites-available/kkfunda

Why Are We Creating This File?

  • NGINX stores all website configurations inside: /etc/nginx/sites-available/

  • We are creating a new config file named: kkfunda

  • Because this file tells NGINX: When traffic comes to this server, where should I send it

  • / means Frontend

  • It forwards request to port 3000

  • Port 3000 = React frontend

  • /api/ means Backend

  • It forwards request to port 5000

  • Port 5000 = Node/Express backend

Paste:

server {
    listen 80;
    server_name _;

    location / {
        proxy_pass http://127.0.0.1:3000;
        proxy_set_header Host $host;
        proxy_set_header X-Real-IP $remote_addr;
    }

    location /api/ {
        proxy_pass http://127.0.0.1:5000;
        proxy_set_header Host $host;
        proxy_set_header X-Real-IP $remote_addr;
    }
}

Enable config:

sudo ln -s /etc/nginx/sites-available/kkfunda /etc/nginx/sites-enabled/
sudo rm /etc/nginx/sites-enabled/default
  • This is very improant NGINX works like this:

  • sites-available → All configs stored

  • sites-enabled → Only active configs

  • By creating a soft link, we are telling NGINX: “Activate this website configuration.”

  • Without this link, NGINX will ignore the file.

Why Remove Default Config?

sudo rm /etc/nginx/sites-enabled/default
  • Ubuntu installs a default website.

  • If we don’t remove it:

  • It may conflict

  • It may override our config

  • It may show default NGINX page

  • So we remove it to avoid conflicts ,Now only our kkfunda config is active.

Test + reload:

sudo nginx -t
sudo systemctl reload nginx
  • This checks:

  • Syntax errors

  • Missing semicolons

  • Wrong format

  • Always test before reload.

  • Reloading applies the new configuration without stopping the server, ensuring no downtime.


Why NGINX Uses 127.0.0.1

  • 127.0.0.1 means: LOCAL MACHINE (loopback address)

  • All services run on same server: NGINX ,React ,Node ,MongoDB

  • So when NGINX routes request internally, it uses:

127.0.0.1

Benefits: Fastest communication , Secure internal routing ,No public exposure ,No network delay


Role of NGINX — System Entry Gate

NGINX is the FIRST COMPONENT that receives requests from users.

It acts as:

  • Reverse proxy

  • Request router

  • Security gateway

  • Traffic manager

Reverse proxy it hides internal ports, improves security, and gives a single public entry point for the application.

How NGINX Routes Requests

Rule defined in config:

Request Path

Destination

/api/*

Backend

everything else

Frontend

Routing Decision

If URL starts with /api
        ↓
     Backend
Else
        ↓
     Frontend
  • This is called: Path-Based Routing
[ Browser ]
     |
     v
[ NGINX ]
   /   \
  v     v
[FE]   [BE]
          |
          v
       [DB]

Without NGINX

Browser → Backend

Problems:

  • Backend port exposed

  • Insecure

  • No routing control

  • Difficult SSL setup


With NGINX

Browser → NGINX → Services

Advantages:

  • Hides backend ports

  • Supports HTTPS

  • Filters traffic

  • Enables load balancing

  • Improves performance


STEP 7 —Now Open Application in Browser

Example:

http://<YOUR_PUBLIC_IP>
  • Now the application loads on port 80 (default HTTP port).

  • User does NOT see 3000 or 5000.

  • Now you can see the KK FUNDA frontend application.

1. Register New User

  • Click on student Login

  • Create a new user by selecting "Create Account," or log in directly if you are already registered.

  • You will see: Name, Email, Password

Provide your details Example :

Name: DevOps
Email: devops@gmail.com
Password: *****
  • Click Signup

  • You will see: Registered successfully

  • That means: Data Saved Full flow working.

2. Login

  • Now you can log in. Since you have registered, you can use the same email and password.
  • Click Login

  • You will be redirected to the Dashboard, indicating that JWT authentication is functioning properly.


3. Dashboard

Now you see: Welcome to KK FUNDA Your DevOps Learning Platform

  • Click on Browse Courses

4. Courses Page

Now you can see all the course content displayed properly.

  • This means the frontend routing is working correctly.

STEP 7 — Verify Database from the Terminal

Now open terminal run:

mongosh

1.Switch to Your Database

Your app uses:

use kkfunda
  • You should see: switched to db kkfunda
use kkfunda

2. To See All Collections

show collections

You should see:

users

3. To See All Registered Users

db.users.find().pretty()
  • This will show all users stored in database.

  • You will see registered users like:

  • That proves: MongoDB is storing users correctly.

Most Important Statement

NGINX is the central traffic controller that decides where every request should go.


Final Conclusion

In this project, we successfully built and deployed a 3-tier web application using React, Node.js, MongoDB, and NGINX on AWS EC2.

We learned how frontend, backend, and database work as separate layers and how NGINX acts as a reverse proxy to securely route traffic. By following the correct deployment order and using environment variables, we implemented a production-style architecture similar to real-world applications.

This project provides a clear understanding of how modern web applications are structured, deployed, and managed in production environments.