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:
Defining backend API base URL
Automatically attaching authentication token
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 |
|---|---|
| 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
User enters credentials
Login.jsx sends request
Backend verifies credentials
Backend generates token
Token returned
Frontend stores token
Future requests automatically authenticated
8. Full Request Flow — Real Example
Request:
POST /api/auth/login
Execution Flow:
User clicks login
React sends request
axios formats request
NGINX receives request
NGINX detects
/apipathNGINX forwards to backend
Backend validates user
MongoDB queried
Backend returns token
Frontend stores token
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
Open AWS Console
Go to EC2 Dashboard
Click Launch Instance
Choose Ubuntu Server
Select t2.medium
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 thefeaturebranch.
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/.envwe write:
REACT_APP_API_URL=/api
REACT_APP_API_URL=/api Meaning
When the frontend makes a backend call
/apiis 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/apiinside the.envfile,
and NGINX forwards that/apitraffic 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 settingREACT_APP_API_URL=/api, all API calls automatically prefix with /api.NGINX then routes
/apitraffic 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 FrontendIt forwards request to port 3000
Port 3000 = React frontend
/api/means BackendIt 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
Why Do We Create Soft Link?
This is very improant
NGINXworks like this:sites-available→ All configs storedsites-enabled→ Only active configsBy 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
kkfundaconfig 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.



