A classroom-ready Expense Tracker MERN app built with:
- MongoDB — database
- Express.js — REST API
- React + Vite — frontend
- NGINX — serves the frontend and proxies
/apito the backend - Docker Compose — runs the full stack with one command
The UI uses a cyber AI inspired brand style: dark navy background, purple gradients, neon cyan highlights, and glass panels.
Browser
|
v
Frontend Container: React static files served by NGINX
|
| /api requests are proxied by NGINX
v
Backend Container: Express.js API
|
v
MongoDB Container: persistent database volume
expense-tracker-mern/
│
├── docker-compose.yml
├── .env.example
├── README.md
│
├── backend/
│ ├── Dockerfile
│ ├── package.json
│ └── src/
│ ├── server.js
│ ├── models/
│ │ └── Expense.js
│ └── routes/
│ └── expenses.js
│
└── frontend/
├── Dockerfile
├── package.json
├── index.html
├── nginx/
│ └── default.conf
└── src/
├── main.jsx
└── styles.css
- Add expense
- Edit expense
- Delete expense
- Filter by category
- Search by title
- Filter by date range
- Summary cards
- Category breakdown bars
- Seed demo data
- Export visible expenses as CSV
- Dockerized frontend, backend, and MongoDB
From the project root:
docker compose up -d --buildOpen the app:
http://localhost
Check containers:
docker compose psCheck logs:
docker compose logs -fStop the app:
docker compose downStop and delete database volume:
docker compose down -vHealth check:
curl http://localhost/api/healthGet expenses:
curl http://localhost/api/expensesSeed demo data:
curl -X POST http://localhost/api/expenses/seedCreate expense:
curl -X POST http://localhost/api/expenses \
-H "Content-Type: application/json" \
-d '{"title":"Coffee","amount":4.5,"category":"Food","note":"Morning coffee"}'Recommended settings for classroom demo:
AMI: Ubuntu Server 22.04 LTS or Ubuntu Server 24.04 LTS
Instance type: t2.micro / t3.micro for demo, t3.small preferred
Storage: 12 GB or more
Inbound rules:
SSH 22 Your IP only
HTTP 80 0.0.0.0/0
Optional for debugging only:
Custom TCP 5000 Your IP only
Custom TCP 27017 Your IP only, generally avoid exposing MongoDB publicly
For this app, the browser only needs port 80 because NGINX proxies API calls internally.
From your laptop:
ssh -i your-key.pem ubuntu@YOUR_EC2_PUBLIC_IPRun these commands on the EC2 Ubuntu machine:
sudo apt update
sudo apt install -y ca-certificates curl unzipsudo install -m 0755 -d /etc/apt/keyrings
sudo curl -fsSL https://download.docker.com/linux/ubuntu/gpg \
-o /etc/apt/keyrings/docker.asc
sudo chmod a+r /etc/apt/keyrings/docker.ascecho \
"deb [arch=$(dpkg --print-architecture) signed-by=/etc/apt/keyrings/docker.asc] https://download.docker.com/linux/ubuntu \
$(. /etc/os-release && echo \"${UBUNTU_CODENAME:-$VERSION_CODENAME}\") stable" | \
sudo tee /etc/apt/sources.list.d/docker.list > /dev/nullsudo apt update
sudo apt install -y docker-ce docker-ce-cli containerd.io docker-buildx-plugin docker-compose-pluginAllow the ubuntu user to run Docker:
sudo usermod -aG docker ubuntu
exitSSH again:
ssh -i your-key.pem ubuntu@YOUR_EC2_PUBLIC_IPVerify Docker:
docker --version
docker compose versionFrom your laptop, from the folder where the zip exists:
scp -i your-key.pem expense-tracker-mern.zip ubuntu@YOUR_EC2_PUBLIC_IP:/home/ubuntu/SSH into EC2:
ssh -i your-key.pem ubuntu@YOUR_EC2_PUBLIC_IPUnzip:
unzip expense-tracker-mern.zip
cd expense-tracker-merndocker compose up -d --buildCheck status:
docker compose psCheck logs:
docker compose logs -fOpen in browser:
http://YOUR_EC2_PUBLIC_IP
curl http://localhost/api/healthSeed demo data:
curl -X POST http://localhost/api/expenses/seedRefresh the browser.
Show running containers:
docker compose psView logs:
docker compose logs -f backendRestart one service:
docker compose restart backendRebuild after code changes:
docker compose up -d --buildStop containers:
docker compose downStop and remove MongoDB data volume:
docker compose down -vFrontend container:
docker exec -it expense-frontend sh
ls /usr/share/nginx/html
cat /etc/nginx/conf.d/default.conf
exitBackend container:
docker exec -it expense-backend sh
ls
node -v
exitMongoDB container:
docker exec -it expense-mongo mongosh expense_tracker
show collections
db.expenses.find().pretty()
exitDockerfile builds one service image.
Docker Compose starts the complete application stack.
For this project:
frontend service:
React app is built and served by NGINX.
backend service:
Express API connects to MongoDB.
mongo service:
Official MongoDB image stores data in a Docker volume.
Best analogy:
Dockerfile = how to build one room
Docker Compose = how to run the entire house with all rooms connected
Check what is using port 80:
sudo lsof -i :80Stop Apache/NGINX if installed on the host:
sudo systemctl stop apache2 || true
sudo systemctl stop nginx || trueRun Compose again:
docker compose up -dCheck backend logs:
docker compose logs backendCheck Mongo logs:
docker compose logs mongodocker compose down
docker compose up -d --builddocker compose down -v
docker compose up -d --buildFor a real production deployment, add:
- HTTPS with NGINX reverse proxy or AWS ALB + ACM certificate
- Authentication and authorization
- MongoDB Atlas or managed database
- CI/CD pipeline
- Environment-specific secrets
- Backups for database volume
- Monitoring and log collection