Creating a Full-Stack MERN Application and Deploying it on an NGINX VPS
Overview of MERN Stack
MERN stands for MongoDB, Express, React, and Node.js. MongoDB is a NoSQL database, Express is a web application framework for Node.js, React is a JavaScript library for building user interfaces, and Node.js is a JavaScript runtime built on Chrome’s V8 JavaScript engine.
Prerequisites
- Node.js installed on your local machine
- A code editor (e.g., VS Code)
- A VPS with root access for deployment (e.g., DigitalOcean, AWS, or Linode)
- Basic knowledge of JavaScript, React, Node.js, and MongoDB
Project Structure
mern-app/
├── backend/
│ ├── config/
│ ├── controllers/
│ ├── middleware/
│ ├── models/
│ ├── routes/
│ ├── app.js
│ └── package.json
├── frontend/
│ ├── public/
│ ├── src/
│ │ ├── components/
│ │ ├── api/
│ │ ├── App.js
│ │ └── index.js
│ └── package.json
└── nginx/
└── mern-app.conf
Create the Backend
Step 1: Create a new directory called mern-app
and navigate into it:
mkdir mern-app
cd mern-app
Step 2: Create a new folder called backend
:
mkdir backend
cd backend
Step 3: Initialize the project and install required packages:
npm init -y
npm install express mongoose dotenv cors
Step 4: Create required folders and files:
mkdir config controllers middleware models routes
touch app.js
Step 5: Configure the app and create a simple REST API:
config/db.js
: Connect to MongoDBmodels/User.js
: Create a User modelroutes/userRoutes.js
: Create user routescontrollers/userController.js
: Implement user controllersmiddleware/auth.js
: Implement authentication middlewareapp.js
: Set up the Express server
Create the Frontend
Step 1: Navigate to the mern-app
folder and create a new folder called frontend
:
cd ..
mkdir frontend
cd frontend
Step 2: Use create-react-app
to initialize the project:
npx create-react-app .
Step 3: Install required packages:
npm install axios react-router-dom
Step 4: Create required folders and files:
mkdir src/components src/api
Step 5: Configure the app and create the frontend:
src/components/
: Create required components (e.g., Header, LoginForm, etc.)src/api/
: Implement API calls to the backendsrc/App.js
: Set up React Router and main app layoutsrc/index.js
: Render the main app component
- Connect Frontend and Backend
- Use Axios in the
frontend/src/api/
files to make API calls to the backend - Enable CORS in the
backend/app.js
to allow cross-origin requests
Production-Ready Configuration
- Use environment variables (
.env
files) for sensitive information - Implement proper error handling and logging
- Use secure middleware like
helmet
andexpress-rate-limit
for security - Enable gzip compression in the NGINX configuration
- Optimize React build using
npm run build
Deployment on NGINX VPS
Step 1: Set up your VPS
- Connect to your VPS via SSH
- Update packages and install required software:
sudo apt update
sudo apt upgrade
sudo apt install nginx git build-essential
Step 2: Install Node.js and MongoDB
- Node.js:
curl -fsSL https://deb.nodesource.com/setup_16.x | sudo -E bash -
sudo apt-get install -y nodejs
- MongoDB:
Follow the instructions for your specific VPS platform: https://docs.mongodb.com/manual/administration/install-on-linux/
Step 3: Clone your project
- Clone your project to a directory, e.g.,
/var/www/mern-app
:
sudo git clone <your-repo-url> /var/www/mern-app
Step 4: Install dependencies and build the frontend
- Navigate to the
backend
andfrontend
directories and install dependencies:
cd /var/www/mern-app/backend
npm install
cd /var/www/mern-app/frontend
npm install
npm run build
Step 5: Set up environment variables for the backend
- Create a
.env
file in thebackend
directory and fill it with the required variables (e.g.,MONGO_URI
,JWT_SECRET
,PORT
)
Step 6: Configure NGINX
- Remove the default NGINX configuration:
sudo rm /etc/nginx/sites-enabled/default
- Create a new NGINX configuration file for your app:
sudo nano /etc/nginx/sites-available/mern-app
- Add the following configuration, replacing the server_name with your domain:
server {
listen 80;
server_name yourdomain.com;
location / {
root /var/www/mern-app/frontend/build;
try_files $uri /index.html;
}
location /api {
proxy_pass http://localhost:5000;
proxy_http_version 1.1;
proxy_set_header Upgrade $http_upgrade;
proxy_set_header Connection 'upgrade';
proxy_set_header Host $host;
proxy_cache_bypass $http_upgrade;
}
}
- Create a symbolic link to enable the configuration:
sudo ln -s /etc/nginx/sites-available/mern-app /etc/nginx/sites-enabled/
- Test and reload the NGINX configuration:
sudo nginx -t
sudo systemctl reload nginx
Step 7: Set up a process manager for the backend
- Install PM2:
sudo npm install -g pm2
- Start the backend app:
cd /var/www/mern-app/backend
pm2 start app.js --name mern-app-backend
- Configure PM2 to start on system boot:
pm2 startup
pm2 save
Step 8: Configure SSL (optional, but recommended)
- Follow the instructions to set up Let’s Encrypt SSL for your domain: https://certbot.eff.org/lets-encrypt/ubuntufocal-nginx
Your full-stack MERN application should now be deployed and accessible via your domain. Remember to follow best practices for security, performance, and production readiness as mentioned earlier in the guide.
9. Backend and Frontend examples
Backend
- Set up Express server (
backend/app.js
):
const express = require('express');
const mongoose = require('mongoose');
const cors = require('cors');
const dotenv = require('dotenv');
dotenv.config();
const app = express();
app.use(cors());
app.use(express.json());
const uri = process.env.MONGO_URI;
mongoose.connect(uri, { useNewUrlParser: true, useUnifiedTopology: true })
.then(() => console.log('MongoDB Connected'))
.catch(err => console.log(err));
const itemsRouter = require('./routes/items');
app.use('/items', itemsRouter);
const port = process.env.PORT || 5000;
app.listen(port, () => console.log(`Server running on port ${port}`));
- Create an Item model (
backend/models/Item.js
):
const mongoose = require('mongoose');
const itemSchema = new mongoose.Schema({
name: {
type: String,
required: true
}
});
module.exports = mongoose.model('Item', itemSchema);
- Create an items route (
backend/routes/items.js
):
const router = require('express').Router();
const Item = require('../models/Item');
router.get('/', async (req, res) => {
try {
const items = await Item.find();
res.json(items);
} catch (err) {
res.status(500).json({ message: err.message });
}
});
router.post('/', async (req, res) => {
const newItem = new Item({ name: req.body.name });
try {
const savedItem = await newItem.save();
res.status(201).json(savedItem);
} catch (err) {
res.status(400).json({ message: err.message });
}
});
module.exports = router;
Frontend
- Set up React Router and main app layout (
frontend/src/App.js
):
import React from 'react';
import { BrowserRouter as Router, Route, Switch } from 'react-router-dom';
import './App.css';
import ItemList from './components/ItemList';
function App() {
return (
<Router>
<div className="App">
<Switch>
<Route path="/" exact component={ItemList} />
</Switch>
</div>
</Router>
);
}
export default App;
- Create a component for displaying and adding items to the list (
frontend/src/components/ItemList.js
):
import React, { useState, useEffect } from 'react';
import axios from 'axios';
const ItemList = () => {
const [items, setItems] = useState([]);
const [newItem, setNewItem] = useState('');
useEffect(() => {
const fetchItems = async () => {
const res = await axios.get('/api/items');
setItems(res.data);
};
fetchItems();
}, []);
const addItem = async (e) => {
e.preventDefault();
const res = await axios.post('/api/items', { name: newItem });
setItems([...items, res.data]);
setNewItem('');
};
return (
<div>
<h1>Item List</h1>
<ul>
{items.map((item) => (
<li key={item._id}>{item.name}</li>
))}
</ul>
<form onSubmit={addItem}>
<input
type="text"
value={newItem}
onChange={(e) => setNewItem(e.target.value)}
/>
<button type="submit">Add Item</button>
</form>
</div>
);
};
export default ItemList;
3. Create an Axios instance to make API calls to the backend (frontend/src/api/index.js
):
import axios from 'axios';
const instance = axios.create({
baseURL: '/api',
});
export default instance;
4. Update the ItemList component to use the Axios instance:
import React, { useState, useEffect } from 'react';
import api from '../api';
const ItemList = () => {
const [items, setItems] = useState([]);
const [newItem, setNewItem] = useState('');
useEffect(() => {
const fetchItems = async () => {
const res = await api.get('/items');
setItems(res.data);
};
fetchItems();
}, []);
const addItem = async (e) => {
e.preventDefault();
const res = await api.post('/items', { name: newItem });
setItems([...items, res.data]);
setNewItem('');
};
return (
<div>
<h1>Item List</h1>
<ul>
{items.map((item) => (
<li key={item._id}>{item.name}</li>
))}
</ul>
<form onSubmit={addItem}>
<input
type="text"
value={newItem}
onChange={(e) => setNewItem(e.target.value)}
/>
<button type="submit">Add Item</button>
</form>
</div>
);
};
export default ItemList;
With these code examples, the backend and frontend should now be functional. You can run both locally using npm start for the frontend and node app.js for the backend. Make sure to follow the previously provided steps to deploy the full-stack MERN application on an NGINX VPS.
0 Comments