Platforms like Versal, NetLife, and Render handle the infrastructure for you. In this tutorial, we’ll dive a layer deeper into understanding the building blocks behind these platforms and work directly with AWS.
You’ll take a small React and Express Notes app and push it straight to AWS. We’ll use EC2 for the API, RDS Postgres for the database, and S3 (optionally CloudFront) for the frontend. If you’re new to AWS, you can turn on the free tier first: https://aws.amazon.com/free.
If you’ve used mostly one-click deployments before, this guide will help you understand what’s going on behind the scenes. This will involve working directly with the core AWS services, focusing only on the pieces that matter so you can see how everything fits together. This will give you more control over cost, security and scaling.
If you just want to grab the generated code, it’s all in the public repo: Umair-Mirza/Death-Notices. You can clone or fork it and follow it without creating a new project from scratch.
Table of Contents
What will you make?
Before touching any buttons in AWS, it’s helpful to know the exact pieces you’re trying to build. At the end of this guide, you’ll have a classic three-tier web app: a browser-based frontend, a backend API, and a database, all talking to each other over the network.
API on EC2 (Express/Node)
Postgres on RDS (free tier eligible)
React/White Frontend on S3 (CDN/CloudFront optional for HTTPS)
Health check
/api/healthAnd on the curd/api/notes
Conditions
You don’t need to be a DevOps expert to follow along, but you should be comfortable running basic commands in Terminal and editing some config files. If you have ever used npm install Before that, then you are at the right place.
Mind map
AWS throws a lot of jargon at you (VPCs, Security Groups, Subnets). This section is the version of the story that happens when someone opens your app in a browser, without any buzzwords. If you can picture that flow, the AWS screens later will feel less scary.
The browser loads the built-in React app from S3 (or CloudFront -> S3).
The browser calls the API over HTTP/HTTPS on EC2
EC2 communicates with RDS Postgres on port 5432 within its VPC
Security Groups: Allow 80/443 in EC2. Only allow 5432 to RDS from EC2 SG
Free Level Basics
AWS can be cheap if you use the free tier, but it can also surprise you with bills if you accidentally leave Orprovision or things running. Here are the main knobs that affect the cost for this tutorial and what to look for.
EC2:
t2.microort3.micro~750 hours/monthRDS:
db.t3.microPostgres/SQL with 20 GB storageS3/CloudFront: Small sites cost money – free tier includes some egress
Save money: Stop EC2 when idle. Delete unused buckets/dbs
Environmental variables
Environment variables are simply configuration values ​​that live outside of your code: ports, database URLs, and allowed initializations. They keep secrets (like DB passwords) out of your git repo and allow the same code to run in different locations (local, staging, production) with different settings.
Background:
PORTfor , for , for , .DATABASE_URL(your RDS endpoint),DATABASE_SSLFor centuries.trueon RDS),CORS_ORIGINFront End:
VITE_API_URL(For example, the API base,https://api.example.com/apiJeez
Step #1 – Run it locally first
Before touching AWS, you want to prove that the app actually works on your own machine. It asks “Is it AWS or my code?” Later debugging. In this step you just install the dependencies and run both backend and frontend in dev mode.
cd mern-notes-aws
cd backend
npm install
cp .env.example .env
npm run dev
cd frontend
npm install
cp .env.example .env
npm run dev
open http://localhost:5173add a note, and check if it persists. /api/health Should come back { status: 'ok' }. If something is broken here, stop and fix it before moving on. AWS will only make debugging difficult.
Step #2 – Click on GitHub (so EC2 can pull)
Your EC2 server in AWS needs a place to pull your code. Using GitHub is the easiest option: you push your code once, then clone the EC2 instance that repo. You can reuse this repo later with CI/CD if you decide to automate deployments.
cd mern-notes-aws
git init
git add .
git commit -m "feat: mern notes app"
git branch -M main
git remote add origin https://github.com//mern-notes-aws.git
git push -u origin main
If you’re following along with my example repo instead of creating your own, you can easily fork Umair-Mirza/Death-Notices And use it as your remote.
Before pushing, make sure you .env is the file Not committed to GitHub. Add it to yours .gitignore So secrets like database passwords never end up in version control:
echo ".env" >> .gitignore
If you have already created .env File locally, double check that doesn’t show up git status Before committing
Step #3 – Create AWS Resources (Quick Path)
RDS (Postgres, Free Tier Template)
RDS (Relational Database Service) is AWS’s way of running a managed database for you. Instead of manually installing Postgres on a VM, you click a few options and AWS handles backups, patching, and high availability. For this app we only need a small, free tier – an eligible Postgres instance.
For more background, you can skim the official Amazon RDS for PostgreSQL documentation.
We’ll start by creating the database layer. The settings below are the minimum you need for a small, production-style Postgres setup that stays within the AWS Free tier while still adhering to basic best practices.
The RDS database creates a Postgres free tier.
Class
db.t3.microstorage 20 GB GP2/GP3.Set master user/pass. You will need them
DATABASE_URL.Public Access: No.
Security Group: Only allow 5432 from EC2 security group.
Enable backup and require SSL. If you want strict certificate validation, download RDSCA.
S3 bucket for frontend
S3 is AWS’ “unlimited hard drive” for files. A React/WhiteApp generates simple HTML, CSS, and JavaScript files, perfect for hosting from S3. Think of S3 as a very simple web server that only serves static files.
If you want to see more options, check out Hosting a Static Website on Amazon S3 Guide.
Now, we will create an S3 bucket to host the React frontend. These options make Balti for stable website hosting while keeping it simple and affordable.
Make a bucket
mern-notes-aws-frontend-.For simple hosting, enable static website hosting and allow public read access, or keep private and use CloudFront + OAC.
If you want rollback safety, turn on the version.
For EC2 API
EC2 is “a computer in the cloud” that you control. You’ll install Node.js on it, pull your code, and run it server.js So that your backend API is always running. The security group associated with this instance acts like a firewall.
If you have never launched an instance before Getting Started with Amazon EC2 The guide goes through the console screens you’ll see.
Finally, we’ll provision a small EC2 instance to run the Express API. The configuration below focuses on a free-tier setup that is safe enough to learn and easy to expand later.
Amazon Linux 2023, Launch Size
t3.micro.Inbound SG: 22 (your IP), 80 (world), 443 if you add HTTPS on instance/albus.
Attach this SG as an authorized source in RDS.
Optional: Cloud Front + Route 53
CloudFront is AWS’s CDN (Content Delivery Network), and Route 53 is their DNS service. You don’t strictly need them to make your app work, but they make it faster and better: your app can live anywhere from places close to users to behind a friendly domain. app.example.com.
For more details, see Getting Started with Amazon CloudFront And Route 53 DNS Developer Guide.
Original: S3 bucket. Default root
index.html. Add OAC if the bucket is private.Apply for an ACM certificate
us-east-1then create a Route 53 A/AAAA alias for distribution.
Step #4 – Configure the EC2 box
Once your EC2 instance is running, you treat it like a clean Linux machine. The commands below install the tools your API needs, pull your code from GitHub, configure environment variables, and run the server safely from production.
Install the basics:
sudo dnf update -y
This command updates all system packages to the latest version. This is a good first step on any new Linux server.
sudo dnf install -y git
Installs git so that the EC2 instance can clone your repository from GitHub.
curl -fsSL | sudo bash -
Adds the official NodeSource repository so you can install the latest version of Node.js (V20). Amazon Linux does not ship with the latest Node version by default.
sudo dnf install -y nodejs
Installs Node.js and npm, which are required to run your Express API.
sudo npm install -g pm2
Installs PM2, a lightweight process manager that keeps your Node app running in the background and restarts it if it crashes or restarts the server.
Extract the code and set the environment variables:
git clone https://github.com//mern-notes-aws.git
cd mern-notes-aws/backend
npm install
cat <<'EOF' > .env
PORT=80
DATABASE_URL=postgres://:@:5432/
DATABASE_SSL=true
CORS_ORIGIN=https://
EOF
Start the API with PM2:
pm2 start server.js --name mern-notes-api
pm2 save
pm2 startup systemd -u ec2-user --hp /home/ec2-user
PM2 is a small process manager that ensures that your Node server keeps running if the machine reboots or processes crash. Test on the box: curl http://localhost/api/health. From your laptop: http:// (Make sure SG 80/443 allows).
Step #5 – Build and Upload the Front End
In development, Wait serves your React app from memory, but in production you want a set of static files that a web server (or S3) can host. npm run build Makes one better dist/ Folders that you sync to S3 so that the browser can load them.
cd frontend
setx VITE_API_URL "https:///api"
npm run build
This sets an environment variable called VITE_API_URL on your local machine. White only exposes the environment variable to the frontend if they start with it VITE_ The previous
Upload:
aws s3 sync dist/ s3://mern-notes-aws-frontend-/ --delete
This uploads the frontend you compiled (dist/) to S3 and removes old files that no longer exist locally, ensuring that the bucket reflects the current version of the app.
Open the S3 website URL or your CloudFront URL.
Step #6 – Quick Troubleshooting
If something doesn’t work the first time, that’s normal, especially with networking and AWS permissions. This section gives you a few quick places to check before randomly changing settings in the console.
API 500S:
pm2 logs mern-notes-api. This is often badDATABASE_URLor SSL flag.DB Connect issues: RDSSG must allow EC2SG – use RDS endpoints.
CORS errors:
CORS_ORIGINYour frontend should match the original exactly.S3 to 403: If you are using static website hosting, allow public read. With CloudFront, keep the bucket private and use OAC.
Blank page: Confirm that you have uploaded
dist/On the right bucket
Step #7 – Save and Save
Once everything is working, you don’t want to accidentally expose your database to the internet or burn up the free tier hours. These are simple, beginner-friendly hard steps that make your setup secure and affordable without turning you into a full-time security engineer.
After setup turn off SSH or switch to SSM session manager.
Use HTTPS (CloudFront + ACM or ALB + ACM).
Keep RDS private and use SSM port forwarding if needed.
Log into PM2 with CloudWatch Agent and add alarms for CPU/status checks.
Snapshot RDS daily and stop EC2 when idle to save hours.
Step #8 – Verify End-to-End
Before you celebrate, run through the app like a real user: open it in a browser, make notes, refresh, and make sure everything behaves as expected. It authenticates your frontend, API, and database.
Load the frontend (S3 or CloudFront).
Create and delete notes. They should be maintained in RDS.
hit
/api/healthFor a quick passion check.
Next Steps
Once you’re comfortable with this manual setup, you can start layering on more advanced tools. The ideas are the same: front end, API and database but you get more automation, security and scalability.
Add Persum + Migrate for robust schemes.
Add Auth (Conginito/auth0) and per-user notes.
Containerize and run on ECS/Fargate or add ALB in front of EC2.
Use terraform/cdk to rebuild this stack with one command.