Maintenance

Site is under maintenance — quizzes are still available.

Go to quizzes
Sponsored Reserved space — layout preview until AdSense is connected
Tutorial

A Complete Guide to Setting Up a MERN Stack Application

Learn how to set up a full MERN stack application from scratch, including MongoDB, Express.js, React, and Node.js. This step-by-step guide walks you through building a task manager with a REST API and React frontend.

July 2026 12 min read 1 views 0 hearts

If you've been around the web development world for any length of time, you've probably heard about the MERN stack. It's one of the most popular combinations for building full-stack applications, and for good reason. MongoDB, Express.js, React, and Node.js work together seamlessly, letting you use JavaScript across the entire stack. At PythonSkillset, we've helped countless developers get started with this powerful setup, and today I'm going to walk you through the entire process from scratch.

What Exactly Is the MERN Stack?

Before we dive into the setup, let's quickly break down what each piece does:

  • MongoDB – A NoSQL database that stores data in flexible, JSON-like documents
  • Express.js – A lightweight web framework for Node.js that handles routing and middleware
  • React – A frontend library for building user interfaces with reusable components
  • Node.js – A JavaScript runtime that lets you run code on the server

The beauty of this stack is that you're writing JavaScript everywhere. Your frontend, backend, and even your database queries can all use the same language. It's a huge productivity boost once you get the hang of it.

Prerequisites

Before we start, make sure you have these installed on your machine:

  • Node.js (version 14 or higher) – You can download it from the official website
  • npm (comes with Node.js) or yarn as an alternative package manager
  • MongoDB – Either install it locally or sign up for a free MongoDB Atlas account
  • A code editor – VS Code is a popular choice, but use whatever you're comfortable with

You can check if Node.js and npm are installed by running these commands in your terminal:

node --version
npm --version

If you see version numbers, you're good to go.

Setting Up the Project Structure

Let's start by creating a folder for our project. Open your terminal and run:

mkdir mern-app
cd mern-app

Inside this folder, we'll create two main directories: one for the backend (server) and one for the frontend (client). This separation keeps things organized and makes deployment easier later on.

mkdir server client

Building the Backend with Node.js and Express

Let's start with the server side. Navigate into the server folder and initialize a new Node.js project:

cd server
npm init -y

The -y flag accepts all the default settings. You can always edit the package.json file later if needed.

Now install the core dependencies:

npm install express mongoose cors dotenv

Here's what each package does: - express – The web framework we'll use to build our API - mongoose – An ODM (Object Data Modeling) library for MongoDB - cors – Middleware that allows cross-origin requests from our React frontend - dotenv – Loads environment variables from a .env file

Let's also install nodemon as a development dependency. It automatically restarts your server when you make changes:

npm install --save-dev nodemon

Creating the Server Entry Point

Create a file called server.js in your server folder. This will be the heart of your backend:

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());

// Connect to MongoDB
mongoose.connect(process.env.MONGODB_URI, {
  useNewUrlParser: true,
  useUnifiedTopology: true
})
.then(() => console.log('Connected to MongoDB'))
.catch(err => console.error('MongoDB connection error:', err));

// Basic route
app.get('/', (req, res) => {
  res.send('MERN Stack API is running');
});

app.listen(PORT, () => {
  console.log(`Server running on port ${PORT}`);
});

Create a .env file in your server folder and add your MongoDB connection string:

MONGODB_URI=mongodb://localhost:27017/mern-app
PORT=5000

If you're using MongoDB Atlas, replace the URI with your Atlas connection string.

Setting Up Nodemon

Open your package.json file and add a start script:

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

Now you can run npm run dev to start the server with automatic reloading.

Creating Your First Model and Routes

Let's build something practical. We'll create a simple task management API. First, create a models folder and a Task model:

// models/Task.js
const mongoose = require('mongoose');

const taskSchema = new mongoose.Schema({
  title: {
    type: String,
    required: true,
    trim: true
  },
  description: {
    type: String,
    default: ''
  },
  completed: {
    type: Boolean,
    default: false
  },
  createdAt: {
    type: Date,
    default: Date.now
  }
});

module.exports = mongoose.model('Task', taskSchema);

Now create a routes folder and add our task routes:

// routes/tasks.js
const express = require('express');
const router = express.Router();
const Task = require('../models/Task');

// Get all tasks
router.get('/', async (req, res) => {
  try {
    const tasks = await Task.find();
    res.json(tasks);
  } catch (err) {
    res.status(500).json({ message: err.message });
  }
});

// Create a task
router.post('/', async (req, res) => {
  const task = new Task({
    title: req.body.title,
    description: req.body.description
  });

  try {
    const newTask = await task.save();
    res.status(201).json(newTask);
  } catch (err) {
    res.status(400).json({ message: err.message });
  }
});

// Update a task
router.patch('/:id', async (req, res) => {
  try {
    const task = await Task.findById(req.params.id);
    if (!task) {
      return res.status(404).json({ message: 'Task not found' });
    }

    if (req.body.title != null) {
      task.title = req.body.title;
    }
    if (req.body.description != null) {
      task.description = req.body.description;
    }
    if (req.body.completed != null) {
      task.completed = req.body.completed;
    }

    const updatedTask = await task.save();
    res.json(updatedTask);
  } catch (err) {
    res.status(400).json({ message: err.message });
  }
});

// Delete a task
router.delete('/:id', async (req, res) => {
  try {
    const task = await Task.findById(req.params.id);
    if (!task) {
      return res.status(404).json({ message: 'Task not found' });
    }

    await task.remove();
    res.json({ message: 'Task deleted' });
  } catch (err) {
    res.status(500).json({ message: err.message });
  }
});

module.exports = router;

Don't forget to update your server.js to use these routes:

const taskRoutes = require('./routes/tasks');
app.use('/api/tasks', taskRoutes);

Setting Up the React Frontend

Now for the fun part – the frontend. Open a new terminal window and navigate to your client folder:

cd client
npx create-react-app .

The dot tells create-react-app to set up the project in the current directory. This might take a minute or two.

Once it's done, install the additional dependencies we'll need:

npm install axios react-router-dom
  • axios – Makes HTTP requests to our backend
  • react-router-dom – Handles client-side routing

Cleaning Up the Default Files

Create-react-app comes with a lot of boilerplate. Let's clean it up. Delete the following files: - src/App.test.js - src/logo.svg - src/reportWebVitals.js - src/setupTests.js

Now open src/App.js and replace everything with:

import React from 'react';
import './App.css';

function App() {
  return (
    <div className="App">
      <h1>MERN Task Manager</h1>
    </div>
  );
}

export default App;

Creating Our First Component

Let's build a simple component that fetches and displays tasks from our backend. Create a new folder called components inside src, then create TaskList.js:

import React, { useState, useEffect } from 'react';
import axios from 'axios';

const TaskList = () => {
  const [tasks, setTasks] = useState([]);
  const [loading, setLoading] = useState(true);

  useEffect(() => {
    fetchTasks();
  }, []);

  const fetchTasks = async () => {
    try {
      const response = await axios.get('http://localhost:5000/api/tasks');
      setTasks(response.data);
      setLoading(false);
    } catch (error) {
      console.error('Error fetching tasks:', error);
      setLoading(false);
    }
  };

  if (loading) {
    return <div>Loading tasks...</div>;
  }

  return (
    <div>
      <h2>Your Tasks</h2>
      {tasks.length === 0 ? (
        <p>No tasks yet. Create one!</p>
      ) : (
        <ul>
          {tasks.map(task => (
            <li key={task._id}>
              <h3>{task.title}</h3>
              <p>{task.description}</p>
              <span>{task.completed ? '✅ Completed' : '⏳ Pending'}</span>
            </li>
          ))}
        </ul>
      )}
    </div>
  );
};

export default TaskList;

Now import this component in your App.js:

import TaskList from './components/TaskList';

function App() {
  return (
    <div className="App">
      <h1>MERN Task Manager</h1>
      <TaskList />
    </div>
  );
}

Connecting Frontend to Backend

You might have noticed we're making requests to http://localhost:5000. This works during development, but you'll run into CORS issues if you're not careful. We already added the cors middleware on the backend, so that's handled.

However, for production, you'll want to set up a proxy. In your client's package.json, add this line:

"proxy": "http://localhost:5000"

This tells the React development server to forward any unknown requests to your backend. It's a cleaner approach than hardcoding the full URL everywhere.

Adding a Form to Create Tasks

Let's make our app actually useful by adding a form to create new tasks. Create TaskForm.js:

import React, { useState } from 'react';
import axios from 'axios';

const TaskForm = ({ onTaskCreated }) => {
  const [title, setTitle] = useState('');
  const [description, setDescription] = useState('');

  const handleSubmit = async (e) => {
    e.preventDefault();

    try {
      const response = await axios.post('http://localhost:5000/api/tasks', {
        title,
        description
      });

      onTaskCreated(response.data);
      setTitle('');
      setDescription('');
    } catch (error) {
      console.error('Error creating task:', error);
    }
  };

  return (
    <form onSubmit={handleSubmit}>
      <input
        type="text"
        placeholder="Task title"
        value={title}
        onChange={(e) => setTitle(e.target.value)}
        required
      />
      <textarea
        placeholder="Description (optional)"
        value={description}
        onChange={(e) => setDescription(e.target.value)}
      />
      <button type="submit">Add Task</button>
    </form>
  );
};

export default TaskForm;

Update your App.js to include the form and refresh the task list when a new task is added:

import React, { useState, useCallback } from 'react';
import TaskList from './components/TaskList';
import TaskForm from './components/TaskForm';
import './App.css';

function App() {
  const [refreshKey, setRefreshKey] = useState(0);

  const handleTaskCreated = useCallback(() => {
    setRefreshKey(prev => prev + 1);
  }, []);

  return (
    <div className="App">
      <h1>MERN Task Manager</h1>
      <TaskForm onTaskCreated={handleTaskCreated} />
      <TaskList key={refreshKey} />
    </div>
  );
}

export default App;

Running the Full Application

You'll need two terminal windows for this. In the first one, start your backend:

cd server
npm run dev

In the second terminal, start your React frontend:

cd client
npm start

Your backend should be running on port 5000, and your frontend on port 3000. Open your browser to http://localhost:3000 and you should see your task manager in action.

What's Next?

This is just the beginning. From here, you can add user authentication with JWT, implement file uploads, connect to a cloud database, or deploy your app to platforms like Heroku or Netlify. The MERN stack is incredibly flexible, and once you understand the basic setup, the possibilities are endless.

At PythonSkillset, we've seen developers build everything from simple to-do apps to complex e-commerce platforms using this stack. The key is to start small, understand how each piece connects, and then gradually add more features as you get comfortable.

Remember, the most important thing is to actually build something. Don't get stuck in tutorial hell. Take what you've learned here, modify it, break it, fix it, and make it your own. That's how real learning happens.

Comments

Questions, corrections, and tips stay visible for everyone reading this page.

0 in thread

Join the discussion

Shown next to your comment.

Up to 4,000 characters

No comments yet

Be the first to leave a note — it helps the next reader.