Full-Stack Web App Setup on a Fresh VPS (With Docker, Node.js, Prisma, Authentication, Frontend, and HTTPS) Print

  • 0

πŸ“„ 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.


Was this answer helpful?

« Back