In this tutorial, we'll learn how to set up a MERN stack on Ubuntu 24.04.
Introduction
Setting up a full MERN (MongoDB, Express, React, Node.js) stack on Ubuntu is a rewarding way to build modern, scalable web applications. In this guide, we’ll walk through each step—why it matters, what commands to run, and how it all pieces together—so that by the end, we’ll have both a backend API and a React frontend talking to a local MongoDB database. Let’s dive in.
Prerequisites
Before we begin, let’s ensure we have the following in place:
- A Ubuntu 24.04 dedicated server or KVM VPS.
- A basic programming knowledge.
How to Set Up a MERN Stack on Ubuntu
1. Prepare Ubuntu & Install Essentials
First, let’s make sure our system is up‐to‐date and has the tools we need (Git, build tools):
sudo apt update && sudo apt upgrade -y
sudo apt install -y git build-essential curl
We install build-essential so that any native modules (e.g. bcrypt) compile correctly, and curl to fetch third‐party repos.
2. Install Node.js (v24 LTS)
We’ll use the official NodeSource repository to get the latest LTS (v24.x as of May 2025):
curl -fsSL https://deb.nodesource.com/setup_24.x | sudo -E bash -
sudo apt install -y nodejs
node -v # should show v24.x.x
npm -v
Node.js provides our JavaScript runtime, npm manages dependencies, and LTS versions ensure long‐term stability.
3. Install MongoDB (v7 Community Edition)
MongoDB 8.x is the current production‐ready release series. We’ll install from MongoDB’s official repo:
curl -fsSL https://www.mongodb.org/static/pgp/server-8.0.asc | \
sudo gpg -o /usr/share/keyrings/mongodb-server-8.0.gpg \
--dearmor
echo "deb [ arch=amd64,arm64 signed-by=/usr/share/keyrings/mongodb-server-8.0.gpg ] https://repo.mongodb.org/apt/ubuntu noble/mongodb-org/8.0 multiverse" | sudo tee /etc/apt/sources.list.d/mongodb-org-8.0.list
sudo apt-get update
sudo apt-get install -y mongodb-org
sudo systemctl enable --now mongod
sudo systemctl status mongod
This gives us a local MongoDB server running on port 27017. We’ll connect to it from our Node.js API.
4. Scaffold the MERN Project Structure
Let’s create a folder to house both backend and frontend:
mkdir mern-ubuntu && cd mern-ubuntu
mkdir backend frontend
Keeping backend and frontend separate simplifies development and deployment.
5. Build the Express+Node.js API
Inside backend, initialize npm and install our dependencies:
cd backend
npm init -y
npm install express mongoose cors dotenv
npm install --save-dev nodemon
- express: minimal web framework
- mongoose: MongoDB ODM for defining schemas and models
- cors: enable Cross-Origin Resource Sharing (so React on another port can talk to us)
- dotenv: load environment variables
- nodemon: auto-reload server during development
Create a file structure:
backend/
├── .env
├── server.js
└── models/
└── Item.js
.env (do not commit this to git):
nano .env
Add following content:
PORT=5000
MONGO_URI=mongodb://localhost:27017/mern_app
models/Item.js:
mkdir models
nano models/Item.js
Add following code:
const mongoose = require('mongoose');
const ItemSchema = new mongoose.Schema({
name: { type: String, required: true },
createdAt: { type: Date, default: Date.now }
});
module.exports = mongoose.model('Item', ItemSchema);
server.js:
nano server.js
Add following code:
require('dotenv').config();
const express = require('express');
const mongoose = require('mongoose');
const cors = require('cors');
const Item = require('./models/Item');
const app = express();
app.use(cors());
app.use(express.json());
// Connect to MongoDB
mongoose.connect(process.env.MONGO_URI, {
useNewUrlParser: true,
useUnifiedTopology: true
})
.then(() => console.log('✅ Connected to MongoDB'))
.catch(err => console.error('❌ MongoDB connection error:', err));
// Routes
app.get('/api/items', async (req, res) => {
const items = await Item.find().sort('-createdAt');
res.json(items);
});
app.post('/api/items', async (req, res) => {
const newItem = new Item({ name: req.body.name });
const saved = await newItem.save();
res.status(201).json(saved);
});
const PORT = process.env.PORT || 5000;
app.listen(PORT, () => console.log(`🚀 Server running on port ${PORT}`));
Add a script to package.json
for development:
nano package.json
Adjust scripts looks like following:
"scripts": {
"start": "node server.js",
"dev": "nodemon server.js"
}
Run npm run dev to verify the API is live on http://localhost:5000
.
6. Create the React Frontend (v19)
In frontend, we’ll use Vite to scaffold React 19 (released December 5, 2024):
cd ../frontend
npm create vite@latest . -- --template react
npm install
Vite gives us a fast development server and bundles with optimized defaults.
Open frontend/package.json, adjust the dev script if needed, then:
In src/App.jsx
, replace with a simple fetch UI:
nano src/App.jsx
Adjust current code with following code:
import { useState, useEffect } from 'react';
function App() {
const [items, setItems] = useState([]);
const [name, setName] = useState('');
useEffect(() => {
fetch('http://localhost:5000/api/items')
.then(res => res.json())
.then(setItems);
}, []);
const addItem = async () => {
if (!name) return;
const res = await fetch('http://localhost:5000/api/items', {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({ name })
});
const created = await res.json();
setItems(prev => [created, ...prev]);
setName('');
};
return (
<div className="container">
<h1>Our MERN App</h1>
<input
value={name}
onChange={e => setName(e.target.value)}
placeholder="New item name"
/>
<button onClick={addItem}>Add Item</button>
<ul>
{items.map(it => (
<li key={it._id}>{it.name}</li>
))}
</ul>
</div>
);
}
export default App;
Ensure CORS is enabled in the Express server (we added app.use(cors())
above).
Run the React dev server:
npm run dev
Your frontend now lives at http://localhost:5173
(or similar Vite port).
7. Running Backend & Frontend Together
During development, keep two terminals open:
Backend:
cd mern-ubuntu/backend
npm run dev
Frontend:
cd mern-ubuntu/frontend
npm run dev
This lets us iterate quickly. For production, we’d build the React app (npm run build) and serve it via Express or a dedicated static‐hosting service.
To access your application on your server's public IP:
npm run dev -- --host 0.0.0.0
8. Next Steps & Best Practices
- Environment Variables: Never commit secrets.
- Error Handling & Validation: Use middleware for robust APIs.
- Security: Sanitize inputs and configure HTTPS.
- Deployment: Consider Dockerizing each component.
- Scaling: For production MongoDB, explore Atlas or replica sets.
In this tutorial, we've learnt how to set up a MERN stack on Ubuntu 24.04. With these steps, we’ve built a modern MERN stack on Ubuntu, using the latest Node.js 24 LTS, MongoDB 8.x, and React 19. Happy coding—and here’s to building great applications together!