How to Start a Full Stack MERN Project: Step-by-Step Guide (2026)

October 6, 2026

Whether you're building a simple to-do app or a production-grade SaaS platform, the MERN stack (MongoDB, Express.js, React, Node.js) is one of the most popular full-stack JavaScript frameworks in the world.

How to Start a Full Stack MERN Project: Step-by-Step Guide (2026)

Whether you're building a simple to-do app or a production-grade SaaS platform, the MERN stack (MongoDB, Express.js, React, Node.js) is one of the most popular full-stack JavaScript frameworks in the world. In this guide, you'll learn the exact step-by-step process to scaffold, structure, and launch any MERN project — including commands, folder structure, and the most useful libraries for every layer of the stack.


What is the MERN Stack?

The MERN stack is a collection of four JavaScript technologies used to build modern, full-stack web applications:

Layer Technology Role M MongoDB NoSQL database E Express.js Backend web framework R React Frontend UI library N Node.js JavaScript runtime

Because everything is JavaScript (front to back), the MERN stack drastically reduces context switching and allows code sharing between layers.


Prerequisites

Before you start, make sure you have the following installed:

Verify your setup:

node -v        # Should print v18.x.x or higher
npm -v         # Should print 9.x.x or higher
git --version  # Should print git version

Step 1 — Project Initialization

Start by creating a root folder that will house both the backend and frontend.

mkdir my-mern-app
cd my-mern-app
git init

Create a root-level .gitignore:

# .gitignore
node_modules/
.env
dist/
build/

Your project will follow a monorepo-style structure with separate server/ and client/ directories inside one root folder.


Step 2 — Backend Setup (Node.js + Express)

2.1 Initialize the Server

mkdir server
cd server
npm init -y

2.2 Install Core Backend Dependencies

# Core
npm install express mongoose dotenv cors

# Development tools
npm install --save-dev nodemon

What each package does:

Package Purpose express HTTP server & routing framework mongoose MongoDB object data modeling (ODM) dotenv Load environment variables from .env cors Handle Cross-Origin Resource Sharing nodemon Auto-restart server on file changes

2.3 Create the Entry Point

Create server/index.js:

const express = require('express');
const mongoose = require('mongoose');
const cors = require('cors');
require('dotenv').config();

const app = express();
const PORT = process.env.PORT || 5000;

// Middleware
app.use(cors());
app.use(express.json());
app.use(express.urlencoded({ extended: true }));

// Routes
app.get('/', (req, res) => {
  res.json({ message: 'MERN API is running' });
});

// MongoDB Connection
mongoose
  .connect(process.env.MONGO_URI)
  .then(() => {
    console.log('MongoDB connected');
    app.listen(PORT, () => console.log(`Server running on port ${PORT}`));
  })
  .catch((err) => console.error('MongoDB connection error:', err));

2.4 Update package.json Scripts

"scripts": {
  "start": "node index.js",
  "dev": "nodemon index.js"
}

Run the server in dev mode:

npm run dev

Step 3 — MongoDB & Mongoose Setup

3.1 Create a .env File

Inside server/:

PORT=5000
MONGO_URI=mongodb+srv://<username>:<password>@cluster.mongodb.net/mydb?retryWrites=true&w=majority
JWT_SECRET=your_super_secret_key
NODE_ENV=development

Tip: Use MongoDB Atlas for a free cloud database. Create a cluster, whitelist your IP, and copy the connection string.

3.2 Create a Mongoose Model

Create server/models/User.js:

const mongoose = require('mongoose');
const bcrypt = require('bcryptjs');

const userSchema = new mongoose.Schema(
  {
    name: {
      type: String,
      required: [true, 'Name is required'],
      trim: true,
    },
    email: {
      type: String,
      required: [true, 'Email is required'],
      unique: true,
      lowercase: true,
    },
    password: {
      type: String,
      required: [true, 'Password is required'],
      minlength: 6,
      select: false, // Don't return password by default
    },
  },
  { timestamps: true }
);

// Hash password before saving
userSchema.pre('save', async function (next) {
  if (!this.isModified('password')) return next();
  this.password = await bcrypt.hash(this.password, 12);
  next();
});

module.exports = mongoose.model('User', userSchema);

3.3 Create a Route

Create server/routes/userRoutes.js:

const express = require('express');
const router = express.Router();
const User = require('../models/User');

// GET all users
router.get('/', async (req, res) => {
  try {
    const users = await User.find();
    res.status(200).json(users);
  } catch (error) {
    res.status(500).json({ message: error.message });
  }
});

module.exports = router;

Register the route in index.js:

const userRoutes = require('./routes/userRoutes');
app.use('/api/users', userRoutes);

Step 4 — Frontend Setup (React + Vite)

4.1 Scaffold the React App

Go back to the project root:

cd ..   # back to my-mern-app/
npm create vite@latest client -- --template react
cd client
npm install

Why Vite over Create React App? Vite is significantly faster for development (instant HMR), lighter, and is now the industry standard.

4.2 Install Core Frontend Dependencies

# Routing
npm install react-router-dom

# HTTP client
npm install axios

# State management
npm install @reduxjs/toolkit react-redux

# Forms & Validation
npm install react-hook-form zod @hookform/resolvers

# UI Component Library (choose one)
npm install @mui/material @emotion/react @emotion/styled  # Material UI
# OR
npm install @shadcn/ui                                     # shadcn/ui (Tailwind-based)

# Tailwind CSS (optional but recommended)
npm install -D tailwindcss postcss autoprefixer
npx tailwindcss init -p

4.3 Configure Tailwind CSS

In client/tailwind.config.js:

/** @type {import('tailwindcss').Config} */
export default {
  content: ['./index.html', './src/**/*.{js,ts,jsx,tsx}'],
  theme: {
    extend: {},
  },
  plugins: [],
};

In client/src/index.css:

@tailwind base;
@tailwind components;
@tailwind utilities;

4.4 Configure Vite Proxy

Edit client/vite.config.js to proxy API calls to your backend during development:

import { defineConfig } from 'vite';
import react from '@vitejs/plugin-react';

export default defineConfig({
  plugins: [react()],
  server: {
    proxy: {
      '/api': {
        target: 'http://localhost:5000',
        changeOrigin: true,
      },
    },
  },
});

This lets you call /api/users from React without CORS issues in development.


Step 5 — Connecting Frontend to Backend

5.1 Create an Axios Instance

Create client/src/api/axios.js:

import axios from 'axios';

const axiosInstance = axios.create({
  baseURL: '/api',
  headers: {
    'Content-Type': 'application/json',
  },
});

// Add auth token to every request automatically
axiosInstance.interceptors.request.use((config) => {
  const token = localStorage.getItem('token');
  if (token) {
    config.headers.Authorization = `Bearer ${token}`;
  }
  return config;
});

export default axiosInstance;

5.2 Fetch Data in a React Component

import { useEffect, useState } from 'react';
import axiosInstance from '../api/axios';

const UserList = () => {
  const [users, setUsers] = useState([]);
  const [loading, setLoading] = useState(true);

  useEffect(() => {
    const fetchUsers = async () => {
      try {
        const { data } = await axiosInstance.get('/users');
        setUsers(data);
      } catch (error) {
        console.error('Error fetching users:', error);
      } finally {
        setLoading(false);
      }
    };

    fetchUsers();
  }, []);

  if (loading) return <p>Loading...</p>;

  return (
    <ul>
      {users.map((user) => (
        <li key={user._id}>{user.name} — {user.email}</li>
      ))}
    </ul>
  );
};

export default UserList;

Step 6 — Authentication

6.1 Install Auth Dependencies (Backend)

cd server
npm install jsonwebtoken bcryptjs

6.2 Create Auth Controller

Create server/controllers/authController.js:

const User = require('../models/User');
const jwt = require('jsonwebtoken');
const bcrypt = require('bcryptjs');

const generateToken = (id) =>
  jwt.sign({ id }, process.env.JWT_SECRET, { expiresIn: '7d' });

// Register
exports.register = async (req, res) => {
  const { name, email, password } = req.body;
  try {
    const existing = await User.findOne({ email });
    if (existing) return res.status(400).json({ message: 'Email already in use' });

    const user = await User.create({ name, email, password });
    res.status(201).json({ token: generateToken(user._id), user });
  } catch (err) {
    res.status(500).json({ message: err.message });
  }
};

// Login
exports.login = async (req, res) => {
  const { email, password } = req.body;
  try {
    const user = await User.findOne({ email }).select('+password');
    if (!user || !(await bcrypt.compare(password, user.password)))
      return res.status(401).json({ message: 'Invalid credentials' });

    res.json({ token: generateToken(user._id), user });
  } catch (err) {
    res.status(500).json({ message: err.message });
  }
};

6.3 Protect Routes with Middleware

Create server/middleware/authMiddleware.js:

const jwt = require('jsonwebtoken');
const User = require('../models/User');

exports.protect = async (req, res, next) => {
  const token = req.headers.authorization?.split(' ')[1];
  if (!token) return res.status(401).json({ message: 'Not authorized' });

  try {
    const decoded = jwt.verify(token, process.env.JWT_SECRET);
    req.user = await User.findById(decoded.id).select('-password');
    next();
  } catch {
    res.status(401).json({ message: 'Token invalid or expired' });
  }
};

Step 7 — Recommended Libraries by Category

Backend Libraries

Category Library Install Command Validation express-validator npm i express-validator File Upload multer npm i multer Cloud Storage cloudinary npm i cloudinary Email nodemailer npm i nodemailer Rate Limiting express-rate-limit npm i express-rate-limit Logging morgan npm i morgan Security helmet npm i helmet API Docs swagger-ui-express npm i swagger-ui-express WebSockets socket.io npm i socket.io Caching redis npm i redis Job Queue bull npm i bull Payments stripe npm i stripe

Frontend Libraries

Category Library Install Command Routing react-router-dom npm i react-router-dom HTTP Client axios npm i axios Server State @tanstack/react-query npm i @tanstack/react-query Global State @reduxjs/toolkit npm i @reduxjs/toolkit react-redux Forms react-hook-form npm i react-hook-form Validation Schema zod npm i zod @hookform/resolvers UI Components shadcn/ui See shadcn docs Charts recharts npm i recharts Animations framer-motion npm i framer-motion Notifications react-hot-toast npm i react-hot-toast Icons lucide-react npm i lucide-react Date Handling date-fns npm i date-fns Real-time socket.io-client npm i socket.io-client


Step 8 — Recommended Folder Structure

This structure scales for both small projects and large enterprise apps:

my-mern-app/
│
├── server/                    # Backend (Node + Express)
│   ├── config/
│   │   └── db.js              # MongoDB connection logic
│   ├── controllers/           # Business logic per resource
│   │   ├── authController.js
│   │   └── userController.js
│   ├── middleware/
│   │   ├── authMiddleware.js  # JWT verification
│   │   └── errorMiddleware.js # Global error handler
│   ├── models/                # Mongoose schemas
│   │   └── User.js
│   ├── routes/                # Express routers
│   │   ├── authRoutes.js
│   │   └── userRoutes.js
│   ├── utils/                 # Helper functions
│   │   ├── sendEmail.js
│   │   └── generateToken.js
│   ├── uploads/               # Multer file uploads (gitignored)
│   ├── .env                   # Environment variables (gitignored)
│   ├── .env.example           # Template for env vars
│   ├── index.js               # Entry point
│   └── package.json
│
├── client/                    # Frontend (React + Vite)
│   ├── public/
│   ├── src/
│   │   ├── api/
│   │   │   └── axios.js       # Axios base instance
│   │   ├── assets/            # Images, fonts
│   │   ├── components/        # Reusable UI components
│   │   │   ├── ui/            # Generic UI (Button, Modal)
│   │   │   └── layout/        # Header, Footer, Sidebar
│   │   ├── features/          # Feature-based modules
│   │   │   ├── auth/
│   │   │   │   ├── authSlice.js
│   │   │   │   ├── LoginPage.jsx
│   │   │   │   └── RegisterPage.jsx
│   │   │   └── users/
│   │   │       ├── userSlice.js
│   │   │       └── UserList.jsx
│   │   ├── hooks/             # Custom React hooks
│   │   │   └── useAuth.js
│   │   ├── pages/             # Route-level page components
│   │   │   ├── HomePage.jsx
│   │   │   ├── DashboardPage.jsx
│   │   │   └── NotFoundPage.jsx
│   │   ├── store/             # Redux store configuration
│   │   │   └── store.js
│   │   ├── utils/             # Helper functions
│   │   │   └── formatDate.js
│   │   ├── App.jsx            # Root component with routes
│   │   ├── main.jsx           # React DOM render
│   │   └── index.css          # Global styles / Tailwind
│   ├── .env                   # VITE_ prefixed env variables
│   ├── index.html
│   ├── vite.config.js
│   └── package.json
│
├── .gitignore
└── README.md

Step 9 — Environment Variables

Backend (server/.env)

PORT=5000
NODE_ENV=development
MONGO_URI=mongodb+srv://user:pass@cluster.mongodb.net/dbname
JWT_SECRET=a_very_long_random_secret_string
JWT_EXPIRES_IN=7d

# Email (optional)
SMTP_HOST=smtp.mailtrap.io
SMTP_PORT=2525
SMTP_USER=your_user
SMTP_PASS=your_pass

# Cloudinary (optional)
CLOUDINARY_CLOUD_NAME=your_cloud_name
CLOUDINARY_API_KEY=your_api_key
CLOUDINARY_API_SECRET=your_api_secret

Frontend (client/.env)

VITE_API_URL=http://localhost:5000/api

Important: In Vite, all environment variables must be prefixed with VITE_ to be exposed to the browser. Access them with import.meta.env.VITE_API_URL.


Step 10 — Scripts & Running the Project

Run Both Servers Concurrently

Install concurrently at the root level to run both servers with one command:

# From the root of my-mern-app/
npm init -y
npm install --save-dev concurrently

Update root package.json:

{
  "scripts": {
    "dev": "concurrently \"npm run dev --prefix server\" \"npm run dev --prefix client\"",
    "build": "npm run build --prefix client",
    "start": "npm start --prefix server"
  }
}

Now run everything with:

npm run dev

This starts:

  • Backend at http://localhost:5000

  • Frontend at http://localhost:5173


Step 11 — Deployment

Deploy Backend to Render (Free Tier Available)

  1. Push your code to GitHub

  2. Go to render.com → New Web Service

  3. Connect your GitHub repo, point to the server/ directory

  4. Set your environment variables in the Render dashboard

  5. Build command: npm install | Start command: node index.js

Deploy Frontend to Vercel

cd client
npm run build
  1. Go to vercel.com → Import Project

  2. Set root directory to client/

  3. Build command: npm run build | Output: dist

  4. Add your environment variable VITE_API_URL pointing to your Render backend URL

Alternative Deployment Options

Platform Backend Frontend Railway Yes - Excellent Yes - Possible Heroku Yes - Classic No Fly.io Yes - Docker-based No Netlify No Yes - Excellent Vercel Yes - Serverless Yes - Best-in-class AWS EC2 Yes - Full control Yes - Full control


Final Tips

For Small Projects:

  • Skip Redux; use React Context or Zustand (npm i zustand) instead

  • Use MongoDB Atlas free tier (512MB is enough to start)

  • Keep everything in a single Express index.js until you need to split it

For Advanced / Production Projects:

  • Add helmet for HTTP security headers: app.use(helmet())

  • Add express-rate-limit to prevent abuse

  • Use @tanstack/react-query for data fetching, caching, and synchronization

  • Add a CI/CD pipeline (GitHub Actions) to auto-deploy on push

  • Write tests with Jest + Supertest (backend) and Vitest + React Testing Library (frontend)

  • Use TypeScript for large teams — create vite app with --template react-ts

Useful VS Code Extensions:

  • ESLint

  • Prettier

  • Thunder Client (API testing inside VS Code)

  • MongoDB for VS Code

  • GitLens


Summary

Here's the complete quick-start checklist:

  • [ ] Create root folder & init git

  • [ ] server/npm init, install Express + Mongoose + dotenv + cors

  • [ ] Create index.js, models, routes, controllers

  • [ ] Set up .env with MONGO_URI and JWT_SECRET

  • [ ] client/ — scaffold with Vite, install axios + react-router-dom

  • [ ] Configure Vite proxy to backend

  • [ ] Add concurrently to root for one-command dev startup

  • [ ] Add auth (JWT + bcrypt)

  • [ ] Build features using feature-based folder structure

  • [ ] Deploy backend to Render, frontend to Vercel

The MERN stack gives you a powerful, unified JavaScript ecosystem to build anything from a weekend side project to a scalable production application. Follow this structure and you will spend less time on boilerplate and more time building actual features.

How to Start a Full Stack MERN Project: Step-by-Step Guide (2026) | Shrisant Adhikari