π Article Summary
This comprehensive guide walks you through setting up a modern full-stack web application on a freshly provisioned VPS. Youβll learn how to:
-
Install system updates and development tools
-
Set up Docker and Docker Compose
-
Scaffold and run a Node.js backend using Express
-
Use Prisma as your ORM
-
Secure APIs with JWT authentication
-
Create a frontend with React or Next.js
-
Configure Nginx as a reverse proxy
-
Enable HTTPS using Certbot and Letβs Encrypt
π Key Concepts & Terms Explained β Glossary for Developers & DevOps
π§© Term | π‘ What It Means | π Why It Matters Here |
---|---|---|
π₯οΈ VPS (Virtual Private Server) | A private slice of a physical server with its own OS and full root access. | Enables full-stack deployments with Docker, Node.js, and more. |
π³ Docker | Container system that packages apps with all their dependencies. | Ensures consistent environments between development and production. |
π¦ Docker Compose | Tool to define and run multi-container apps via docker-compose.yml . |
Spins up backend, frontend, and Nginx togetherβsimplifies orchestration. |
βοΈ Node.js | A JavaScript runtime built on V8 for server-side development. | Runs the Express backend logic and handles API endpoints. |
π£οΈ Express.js | Lightweight web framework for Node.js. | Powers REST APIs like /login , /register , etc. |
π NPM / package.json | Node Package Manager and its manifest file. | Manages dependencies like bcrypt , jwt , prisma , and defines scripts. |
π§ ORM (Object Relational Mapper) | Maps DB tables to code objects, avoiding raw SQL. | Simplifies data operations using intuitive syntax. |
π Prisma | Modern ORM for Node.js with auto-generated queries. | Handles DB connections, schema, and migration logic efficiently. |
ποΈ Migration | Version-controlled changes to the DB schema. | Keeps database structure in sync across dev/staging/production. |
ποΈ SQLite / PostgreSQL / MySQL | Relational DBsβSQLite for dev, PostgreSQL/MySQL for production. | Choose based on scale, performance, and production needs. |
π JWT (JSON Web Token) | A compact, signed token for user authentication. | Enables secure and stateless auth between frontend and backend. |
π§ Bcrypt | Secure hashing algorithm for passwords. | Stores hashed passwords for robust authentication security. |
π§βπ¨ React / Next.js | React = frontend UI library; Next.js = React + server-side rendering. | Renders dynamic UIs and fetches API data. |
π Axios | HTTP client for making API requests. | Used in React to connect to Express backend securely. |
π¦ Nginx | Web server and reverse proxy for routing and load balancing. | Forwards /api/* to backend and serves static frontend. |
π Reverse Proxy | Public-facing server that redirects traffic internally. | Allows backend/frontend separation behind port 80/443. |
π‘οΈ Certbot | Letβs Encrypt automation tool for HTTPS setup. | Quickly secures your site with free SSL. |
π Letβs Encrypt | Free SSL certificate authority. | Powers HTTPS without the cost of paid certs. |
π .env File | Configuration file storing environment variables. | Keeps secrets like DB credentials and JWT keys secure. |
𧬠docker-compose.yml | Blueprint to define Docker services and networks. | Used to start the entire stack in one command. |
ποΈ Volume (Docker) | Persistent data store outside of containers. | Ensures data like logs, DB files, etc. are not lost on container restart. |
π PM2 | Node.js process manager for production. | Keeps backend alive, auto-restarts on crash, and logs events. |
π€ GitHub Actions (CI/CD) | Automates builds, tests, and deployments from your repo. | Ensures consistent releases and automated testing. |
π CORS | Browser rule for cross-domain requests. | Enables frontend on port 3000 to access backend on 5000. |
π§° Helmet | Express middleware for setting secure HTTP headers. | Prevents common web attacks (XSS, clickjacking). |
π§± Rate Limiter | Middleware that limits requests per IP. | Prevents brute-force login attempts and abuse. |
βοΈ CI/CD | Continuous Integration / Deployment pipeline. | Automates building and deploying full-stack apps. |
π JWT Refresh Flow (Optional) | Issues long-term refresh tokens and short-term access tokens. | Prevents session expiry without user re-login. |
π Twelve-Factor App | 12 rules for modern app development (env vars, logs, stateless). | Your project structure aligns with these SaaS best practices. |
βοΈ 1. Start with a Fresh VPS
sudo apt update && sudo apt upgrade -y
sudo apt install curl wget git unzip nano -y
π³ 2. Install Docker and Docker Compose
curl -fsSL https://get.docker.com -o get-docker.sh
sh get-docker.sh
sudo usermod -aG docker $USER
newgrp docker
# Install Docker Compose v2
sudo apt install docker-compose-plugin
ποΈ 3. Project Structure Example
project/
βββ backend/
β βββ src/
β βββ prisma/
β βββ .env
β βββ package.json
β βββ Dockerfile
βββ frontend/
β βββ public/
β βββ src/
β βββ .env
β βββ package.json
β βββ Dockerfile
βββ docker-compose.yml
βββ nginx/
βββ default.conf
π 4. Backend Setup (Node.js + Express)
Initialize Backend:
mkdir -p backend/src backend/prisma
cd backend
npm init -y
npm install express dotenv bcryptjs jsonwebtoken @prisma/client
npm install -D nodemon prisma
Update package.json
"scripts": {
"dev": "nodemon src/index.js",
"start": "node src/index.js"
}
Sample src/index.js
require('dotenv').config();
const express = require('express');
const app = express();
const port = process.env.PORT || 5000;
app.use(express.json());
app.get('/', (req, res) => res.send('API Running'));
app.listen(port, () => {
console.log(`π Backend server is running at http://localhost:${port}`);
});
Create .env
PORT=5000
DATABASE_URL="file:./dev.db"
JWT_SECRET=your_secret_key
π§© 5. Add Prisma ORM
Initialize Prisma:
npx prisma init
Example prisma/schema.prisma
datasource db {
provider = "sqlite"
url = env("DATABASE_URL")
}
generator client {
provider = "prisma-client-js"
}
model User {
id Int @id @default(autoincrement())
email String @unique
password String
}
Apply Migration and Generate Client:
npx prisma migrate dev --name init
Use in Code:
const { PrismaClient } = require('@prisma/client');
const prisma = new PrismaClient();
π 6. JWT Authentication
Register & Login Endpoints (Basic Example)
const jwt = require('jsonwebtoken');
const bcrypt = require('bcryptjs');
// Registration
app.post('/register', async (req, res) => {
const { email, password } = req.body;
const hashed = await bcrypt.hash(password, 10);
const user = await prisma.user.create({ data: { email, password: hashed } });
res.json(user);
});
// Login
app.post('/login', async (req, res) => {
const { email, password } = req.body;
const user = await prisma.user.findUnique({ where: { email } });
if (!user || !(await bcrypt.compare(password, user.password))) {
return res.status(401).json({ error: 'Invalid credentials' });
}
const token = jwt.sign({ userId: user.id }, process.env.JWT_SECRET);
res.json({ token });
});
π¨ 7. Frontend Setup (React or Next.js)
Example: Create React App
npx create-react-app frontend
cd frontend
npm install axios react-router-dom
.env
REACT_APP_API_URL=http://localhost:5000
Fetching API from React
import axios from 'axios';
const login = async () => {
const response = await axios.post(`${process.env.REACT_APP_API_URL}/login`, {
email: 'test@example.com',
password: 'secret'
});
localStorage.setItem('token', response.data.token);
};
π§ 8. Dockerize Backend and Frontend
Backend Dockerfile
FROM node:18
WORKDIR /app
COPY . .
RUN npm install
CMD ["npm", "run", "dev"]
Frontend Dockerfile
FROM node:18
WORKDIR /app
COPY . .
RUN npm install && npm run build
EXPOSE 3000
CMD ["npx", "serve", "-s", "build"]
π§± 9. Docker Compose
version: '3.8'
services:
backend:
build: ./backend
ports:
- "5000:5000"
env_file:
- ./backend/.env
volumes:
- ./backend:/app
frontend:
build: ./frontend
ports:
- "3000:3000"
env_file:
- ./frontend/.env
volumes:
- ./frontend:/app
nginx:
image: nginx:latest
volumes:
- ./nginx/default.conf:/etc/nginx/conf.d/default.conf
ports:
- "80:80"
depends_on:
- backend
- frontend
π 10. Nginx Reverse Proxy Setup
Example nginx/default.conf
server {
listen 80;
location /api/ {
proxy_pass http://backend:5000/;
proxy_set_header Host $host;
}
location / {
proxy_pass http://frontend:3000/;
proxy_set_header Host $host;
}
}
π 11. HTTPS via Certbot (Letβs Encrypt)
sudo apt install certbot python3-certbot-nginx -y
sudo certbot --nginx -d yourdomain.com -d www.yourdomain.com
Auto-renew:
sudo crontab -e
# Add:
0 3 * * * certbot renew --quiet
π§© 12. Next Steps
Once your full-stack application is functional, you can take it to the next level with these enhancements and production-level improvements:
π Use PostgreSQL or MySQL with Prisma
Instead of the default SQLite or in-memory databases, integrate a robust relational database like PostgreSQL or MySQL.
-
Install the client library:
npm install @prisma/client npm install prisma --save-dev npm install pg # For PostgreSQL # or npm install mysql2 # For MySQL
-
Configure Prisma schema (
prisma/schema.prisma
):datasource db { provider = "postgresql" url = env("DATABASE_URL") }
-
Set the database URL in
.env
:DATABASE_URL="postgresql://username:password@localhost:5432/mydb"
-
Migrate the database:
npx prisma migrate dev --name init
π§ͺ Add Tests (Jest, Supertest)
Improve code reliability with automated tests:
-
Install testing libraries:
npm install --save-dev jest supertest @types/jest ts-jest
-
Basic test example (
tests/app.test.js
):const request = require('supertest'); const app = require('../src/app'); describe('GET /', () => { it('should return 200 OK', async () => { const res = await request(app).get('/'); expect(res.statusCode).toEqual(200); }); });
-
Add to package.json:
"scripts": { "test": "jest" }
π¦ Set Up CI/CD (GitHub Actions)
Automate your testing and deployment process.
-
Sample GitHub Actions workflow
.github/workflows/ci.yml
:name: CI on: push: branches: [main] jobs: build: runs-on: ubuntu-latest steps: - uses: actions/checkout@v3 - name: Set up Node.js uses: actions/setup-node@v3 with: node-version: '18' - run: npm install - run: npm run test
π Deploy on VPS
-
Clone your repo on the server
-
Use PM2 or Docker to manage Node.js processes
-
Set up Nginx as a reverse proxy
Example PM2 setup:
npm install pm2 -g
pm run build
pm start
pm2 start index.js --name myapp
pm2 save && pm2 startup
π‘οΈ Add CORS, Helmet, Rate Limiters
Secure your backend with best practices.
-
CORS:
npm install cors
const cors = require('cors'); app.use(cors());
-
Helmet:
npm install helmet
const helmet = require('helmet'); app.use(helmet());
-
Rate Limiter:
npm install express-rate-limit
const rateLimit = require('express-rate-limit'); const limiter = rateLimit({ windowMs: 15 * 60 * 1000, max: 100 }); app.use(limiter);
With these enhancements, your project will be more robust, secure, testable, and production-ready.