MERN (MongoDB, Express, React, Node.js) and MEAN (with Angular) are popular full-stack JavaScript stacks. In production they become three pieces: a static frontend (the built React or Angular app), a Node.js API (Express), and a MongoDB database. Here’s how to deploy all three on one VPS, with Nginx in front.

Architecture on the VPS

Browser ──HTTPS──► Nginx
                    ├── /            → static frontend files (React/Angular build)
                    └── /api/        → Node.js (Express) on 127.0.0.1:3000
                                          └── MongoDB on 127.0.0.1:27017

Requirements

1. MongoDB (local, secured)

Install MongoDB Community from MongoDB’s official repository. See install MongoDB on Ubuntu. Then:

  • keep bindIp: 127.0.0.1 in /etc/mongod.conf (never expose 27017 publicly);
  • enable authentication, and create an app user:
// mongosh
use admin
db.createUser({ user: "admin", pwd: passwordPrompt(), roles: ["root"] })
use myapp
db.createUser({ user: "myapp", pwd: passwordPrompt(), roles: [{ role: "readWrite", db: "myapp" }] })
# /etc/mongod.conf
security:
  authorization: enabled
sudo systemctl restart mongod

Or use a managed MongoDB service, and connect over TLS.

2. The API (Express)

sudo mkdir -p /opt/myapp && sudo chown $USER /opt/myapp
cd /opt/myapp && git clone https://git.example.com/team/myapp.git .
cd server && npm ci --omit=dev

Configuration via environment variables (server/.env, chmod 600):

NODE_ENV=production
PORT=3000
MONGODB_URI=mongodb://myapp:<password>@127.0.0.1:27017/myapp?authSource=myapp
JWT_SECRET=<long random string>

Run it with PM2, so it restarts on crashes and at boot:

sudo npm install -g pm2
pm2 start server.js --name api -i 2      # 2 instances (cluster mode)
pm2 save && pm2 startup                  # follow the printed command

Make sure Express listens on 127.0.0.1, or that the firewall blocks port 3000 externally. See PM2.

3. Build the frontend

cd /opt/myapp/client
npm ci
npm run build            # React (Vite): dist/   Angular: dist/<project>/browser/
sudo mkdir -p /var/www/myapp && sudo cp -r dist/* /var/www/myapp/

Set the API base URL to a relative path (/api) at build time, so the frontend and API share the domain. That avoids CORS headaches.

4. Nginx: static files + API proxy

server {
    server_name app.example.com;
    root /var/www/myapp;
    index index.html;

    location /api/ {
        proxy_pass http://127.0.0.1:3000;
        proxy_http_version 1.1;
        proxy_set_header Host $host;
        proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
        proxy_set_header X-Forwarded-Proto $scheme;
        proxy_set_header Upgrade $http_upgrade;       # websockets / socket.io
        proxy_set_header Connection "upgrade";
    }

    location / {
        try_files $uri $uri/ /index.html;              # SPA client-side routing
    }

    location ~* \.(js|css|png|jpg|svg|woff2)$ {
        expires 30d;
        access_log off;
    }
}

try_files … /index.html makes deep links (/dashboard/settings) work with React Router and the Angular router. See deploy a React app with Nginx and deploy Angular with Nginx.

sudo nginx -t && sudo systemctl reload nginx
sudo certbot --nginx -d app.example.com

In Express, set app.set('trust proxy', 1), so secure cookies and client IPs work behind Nginx.

Docker alternative

Run api, mongo and an nginx (serving the built frontend) as one Compose stack, with MongoDB’s data in a named volume, and no published Mongo port. See Docker Compose in production.

Deploying updates

cd /opt/myapp && git pull
cd server && npm ci --omit=dev && pm2 reload api        # zero-downtime reload in cluster mode
cd ../client && npm ci && npm run build && sudo rsync -a --delete dist/ /var/www/myapp/

Automate this with CI. See GitHub Actions deploy to VPS and zero-downtime Node.js deploys.

Backups

mongodump --uri="mongodb://admin:<pw>@127.0.0.1:27017/?authSource=admin" --archive=/backups/mongo-$(date +%F).gz --gzip

See MongoDB backup and restore.

Quick reference

Piece Setup
Frontend Static build served by Nginx, try_files … /index.html
API Express on 127.0.0.1:3000 via PM2 (cluster)
Database MongoDB on localhost, auth enabled
Proxy /api/ → Node, WebSocket headers
HTTPS certbot --nginx

Related: deploy Next.js on a VPS and twelve-factor app.

  • Node.js
  • MongoDB
  • Deployment