In this tutorial, we'll learn how to set up a MERN stack on AlmaLinux 9.
We’re going to walk through every step of standing up a full MERN stack on AlmaLinux 9—MongoDB for data storage, Express.js for our API, React for the front-end, and Node.js as our runtime. By the end, we’ll have a working “Hello World” API talking to a React app. Let’s dive in.
Prerequisites
Before we begin, let’s ensure we have the following in place:
- A AlmaLinux 9 dedicated server or KVM VPS.
- A basic programming knowledge.
How to Set Up a MERN Stack on AlmaLinux
1. Prepare and Update Our System
First, let’s make sure our AlmaLinux 9 server is up-to-date and has the tools we need.
sudo dnf update -y
sudo dnf install -y git curl wget
- dnf update brings all packages to the latest security and bug-fix versions.
- We add git, curl, and wget since we’ll fetch repos and installers later.
2. Install Node.js (LTS)
We want a stable, long-term support version—at the moment that’s Node.js 24.x. We’ll pull it from NodeSource.
curl -fsSL https://rpm.nodesource.com/setup_24.x | sudo bash -
sudo dnf install -y nodejs
- The script configures the NodeSource repo for AlmaLinux 9.
- After that, dnf install nodejs gives us both node and npm.
Verify versions:
node -v # should print v24.x.x
npm -v # will print npm 11.x or similar
3. Install MongoDB Community Edition
AlmaLinux 9 doesn’t ship MongoDB by default, so we’ll add the official MongoDB repo for RPM-based distros.
cat <<EOF | sudo tee /etc/yum.repos.d/mongodb-org-8.0.repo
[mongodb-org-8.0]
name=MongoDB Repository
baseurl=https://repo.mongodb.org/yum/redhat/9/mongodb-org/8.0/x86_64/
gpgcheck=1
enabled=1
gpgkey=https://pgp.mongodb.com/server-8.0.asc
EOF
Now, install MongoDB and start the service using following command:
There is some issue the mongodb-mongosh and OpenSSL v3. By default mongodb-mongosh gets install with mongodb-org package. We are not sure what is the issue but we can go with following solution:
We need to install mongodb-mongosh-shared-openssl3 first and than mongodb-org
dnf install -qy mongodb-mongosh-shared-openssl3
sudo dnf install -y mongodb-org
sudo systemctl enable --now mongod
- We define a new repo file pointing at MongoDB 8.0 for RHEL 9 (compatible with AlmaLinux).
- install mongodb-org brings in the server, shell, and tools.
- enable --now mongod starts MongoDB and ensures it launches on boot.
This way we can use mongosh command and get into database cli.
Quick test:
mongosh --eval 'db.runCommand({ connectionStatus: 1 })'
You should see “ok: 1” indicating the server is running.
{
authInfo: { authenticatedUsers: [], authenticatedUserRoles: [] },
ok: 1
}
4. Scaffold the MERN Project Structure
Let’s create a folder to house both backend and frontend:
mkdir mern-almalinux && cd mern-almalinux
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-almalinux/backend
npm run dev
Frontend:
cd mern-almalinux/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 AlmaLinux 9. With these steps, we’ve built a modern MERN stack on AlmaLinux 9, using the latest Node.js 24 LTS, MongoDB 8.x, and React 19. Happy coding—and here’s to building great applications together!