Building a Simple MERN Stack App—A Hands-On Walkthrough 💻

Srikant SahuSrikant Sahu
5 min read

Welcome to Day 1 of my daily blogging series! Today, we’ll dive into creating a minimal MERN stack app from scratch. By the end of this post, you’ll have:

  • A basic Express and Node.js backend with a single API endpoint.

  • A MongoDB database connected via Mongoose.

  • A React front end that fetches data from your backend.

  • A deployed version of your app (optional).

Note: If you’re new to any piece of this stack, feel free to bookmark this tutorial and revisit as you get more familiar. Let’s get started!


Project Setup & Folder Structure

First, create a new folder for your project. In your terminal:

mkdir mern-todo-app
cd mern-todo-app

Inside, we’ll have two main directories: server and client:

mern-todo-app/
├── server/
└── client/
  • server/: All backend-related code (Express, Node.js, MongoDB).

  • client/: React front end (using Create React App).


Backend: Express & MongoDB

Initialize the Server

Navigate to server/ and initialize a Node project:

cd server
npm init -y

Install dependencies:

npm install express mongoose cors dotenv
  • express: Web framework.

  • mongoose: MongoDB ODM.

  • cors: Allow cross-origin requests.

  • dotenv: Manage environment variables.

In server/, create a file named server.js:

// server/server.js
import express from 'express';
import mongoose from 'mongoose';
import cors from 'cors';
import dotenv from 'dotenv';

dotenv.config();

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

// Middleware
app.use(cors());
app.use(express.json());

// MongoDB Connection
mongoose
  .connect(process.env.MONGO_URI, {
    useNewUrlParser: true,
    useUnifiedTopology: true,
  })
  .then(() => console.log('✅ MongoDB connected'))
  .catch((err) => console.error('❌ MongoDB connection error:', err));

// Simple Mongoose Schema
const todoSchema = new mongoose.Schema({
  text: { type: String, required: true },
  completed: { type: Boolean, default: false },
});

const Todo = mongoose.model('Todo', todoSchema);

// Routes
app.get('/api/todos', async (req, res) => {
  try {
    const todos = await Todo.find();
    res.json(todos);
  } catch (err) {
    res.status(500).json({ message: err.message });
  }
});

app.post('/api/todos', async (req, res) => {
  const { text } = req.body;
  const newTodo = new Todo({ text });
  try {
    const savedTodo = await newTodo.save();
    res.status(201).json(savedTodo);
  } catch (err) {
    res.status(400).json({ message: err.message });
  }
});

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

Add a .env file in server/:

MONGO_URI=your_mongodb_connection_string_here

Run the Server

In server/ directory, add a start script to package.json:

"scripts": {
  "start": "node --experimental-modules server.js"
}

Then run:

npm start

You should see “MongoDB connected” and “Server running on port 5000” in your console.


Front End: React Application

Bootstrap React

In the project root (mern-todo-app/), run:

npx create-react-app client

Navigate into client/:

cd client

Install Axios (or fetch is fine, but Axios simplifies JSON calls):

npm install axios

Building the Todo Interface

In client/src/App.js, replace with:

// client/src/App.js
import React, { useState, useEffect } from 'react';
import axios from 'axios';

function App() {
  const [todos, setTodos] = useState([]);
  const [text, setText] = useState('');

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

  const fetchTodos = async () => {
    try {
      const res = await axios.get('http://localhost:5000/api/todos');
      setTodos(res.data);
    } catch (err) {
      console.error(err);
    }
  };

  const addTodo = async (e) => {
    e.preventDefault();
    if (!text) return;
    try {
      await axios.post('http://localhost:5000/api/todos', { text });
      setText('');
      fetchTodos();
    } catch (err) {
      console.error(err);
    }
  };

  return (
    <div style={styles.container}>
      <h1 style={styles.heading}>My TODO App (MERN Stack)</h1>
      <form onSubmit={addTodo} style={styles.form}>
        <input
          type="text"
          value={text}
          onChange={(e) => setText(e.target.value)}
          placeholder="Enter a task..."
          style={styles.input}
        />
        <button type="submit" style={styles.button}>
          Add Todo
        </button>
      </form>
      <ul style={styles.list}>
        {todos.map((todo) => (
          <li key={todo._id} style={styles.listItem}>
            {todo.text}
          </li>
        ))}
      </ul>
    </div>
  );
}

const styles = {
  container: {
    maxWidth: '600px',
    margin: '2rem auto',
    padding: '1rem',
    textAlign: 'center',
    fontFamily: 'Arial, sans-serif',
  },
  heading: {
    marginBottom: '1.5rem',
  },
  form: {
    display: 'flex',
    justifyContent: 'center',
    marginBottom: '1rem',
  },
  input: {
    padding: '0.5rem',
    width: '60%',
    fontSize: '1rem',
  },
  button: {
    padding: '0.5rem 1rem',
    marginLeft: '0.5rem',
    fontSize: '1rem',
  },
  list: {
    listStyle: 'none',
    padding: 0,
  },
  listItem: {
    background: '#f8f8f8',
    margin: '0.5rem 0',
    padding: '0.75rem',
    borderRadius: '4px',
    textAlign: 'left',
  },
};

export default App;

Start the React app:

npm start

It should open on http://localhost:3000, showing a header, input field, and empty list.


Testing It Out

  • Make sure your backend server (localhost:5000) is running.

  • In the front end, type a new to-do (e.g., “Learn MERN Stack”) and click Add Todo.

  • Check the list below—your new task should appear, pulled from MongoDB.


(Optional) Deploying to Production

If you want to deploy, here’s a quick outline:

Backend (Heroku, Render, or similar):

  • Update server.js to serve the front end in production:

      // After your API routes in server.js
      if (process.env.NODE_ENV === 'production') {
        app.use(express.static('../client/build'));
        app.get('*', (req, res) =>
          res.sendFile(path.resolve(__dirname, '../client', 'build', 'index.html'))
        );
      }
    
  • Push to Heroku, ensure MONGO_URI is set in Heroku’s config vars.

Frontend (Vercel, Netlify, or serve from Heroku):

  • In client/, run npm run build to generate a production bundle.

  • If using separate hosts, deploy the build/ folder (Netlify/Vercel).

  • Update the Axios base URL to your live backend URL.


Wrapping Up

Congratulations! 🎉 You’ve built a functional MERN stack todo app:

  • Express/Node.js server with API endpoints

  • MongoDB for data storage via Mongoose

  • React front end fetching and displaying data

This basic project lays the foundation for scaling up—adding authentication, advanced routing, Redux/Context for state, or responsive styling with Tailwind. In Day 2, we’ll explore state management in React and dive deeper into Hooks. Stay tuned!

In this tutorial, you'll learn to create a minimal MERN stack app from scratch. By the end, you'll have a basic Express and Node.js backend with MongoDB connected via Mongoose, a React front end fetching data from your backend, and an optional deployed version of your app. The guide includes full setup instructions, from project initialization to running both the server and React app locally. Stay tuned for Day 2, where we'll explore state management in React with Hooks.

0
Subscribe to my newsletter

Read articles from Srikant Sahu directly inside your inbox. Subscribe to the newsletter, and don't miss out.

Written by

Srikant Sahu
Srikant Sahu

I am a MERN stack developer, committed to build high-quality web applications that meet the needs of my clients. React.js, MongoDB, Express.js, and Node.js to create scalable and robust web applications.