Nur Islam Sport programmer.

The MERN stack tutorial

21 min read 6006

MERN Stack: Complete Guide

Editor’s note: This MERN stack tutorial was last updated on 2 December 2022 to expand and update the frontend portion of the tutorial. This update includes new Axios, React Hooks, and authorization sections.

This tutorial is all about the MERN stack. We’ll outline the basics of the MERN stack and demonstrate how to use it by developing a simple CRUD application from scratch.

To show how the MERN stack works, we’ll first configure the server side by connecting Node.js and Express.js to MongoDB on the backend. Then, we’ll create some APIs. After that, we’ll walk you through building the frontend, using React to build our user interfaces. Once both are complete, we’ll connect the frontend to the backend.

Jump ahead:

What is the MERN stack?

The phrase MERN stack comprises the following technologies that allow for faster application development:

  • MongoDB: A cross-platform document-oriented database program
  • Express.js: A web application framework for Node.js
  • React: JavaScript library for building user interfaces
  • Node.js: An open source, cross-platform, JavaScript run-time environment that executes JavaScript code outside of a browser

MongoDB, Express.js, and Node.js help you build the backend of an application, while React powers the frontend. Node.js and React rank as the two most popular web frameworks and technologies, according to the most recent Stack Overflow developer survey. And MongoDB is also one of the most popular databases for developers. About 16,535,063 websites use React to power their frontend. You can easily assume how stable and popular these technologies are.

Learning the MERN stack will help you become a full-stack developer. The demand for MERN stack developers is also high. However, it takes time to be a good MERN stack developer. So, before learning technologies like React or Node.js, it is important to ensure that your fundamentals are strong. A lot of developers fail to be good MERN stack developers because they lack the fundamental knowledge of JavaScript.

If you’re a visual learner with some time on your hands, check out this comprehensive, MERN stack video tutorial:

Server setup with Express.js and Node.js

This demo is designed to highlight the MERN setup. The objective is to develop a simple project with the best possible structure so that you can use it as a boilerplate and elevate your MERN stack projects to meet industry standards.

To begin our MERN stack tutorial, we’ll show you how to set up a server with Express.js and Node.js.

npm package initialization

To create a project folder, enter the folder through the terminal, then run $ npm init. Then, it will ask you some questions about the package name, version, entry point, and more.

Hit Enter if you want to keep the default. After that, you will get something like this:

MERN Stack Creating Our package.json File

Select yes, and you’re ready to go. This will create a file named package.json.

Installing the dependencies

Next, we’ll add some dependencies with $ npm i express mongoose body-parser config. Type or copy the command above and hit the Enter button. You’ll see something like this:

Adding Project File Dependencies MERN Stack
Here’s what we get in the code above:

  • body-parser: Allows us to get the data throughout the request
  • express: Is our main framework
  • mongoose: Is used to connect and interact with MongoDB
  • config: This lets you define default parameters for your application

Now, we’ll add nodemon as a dev dependency. If you don’t want to add this, you can skip it — it’s optional. Install it with $ npm i -D nodemon. To use nodemon, add "app": "nodemon app.js" to your scripts tag under the package.json file.

Nodemon is a utility that will monitor for any changes in your source and automatically restart your server. The app.js is the entry point for the application. It is also important to define a start script here with "start": "node app.js". This will define the start script of the application.

After that, your package.json should look like this:

MERN Stack package.json File with Dependencies

Setting the entry point

Now, create a file named app.js for our entry point. You can create this from the project folder with the $ touch app.js command (on macOS).

Then, paste the following code:

// app.js

const express = require('express');

const app = express();

app.get('/', (req, res) => res.send('Hello world!'));

const port = process.env.PORT || 8082;

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

After that, run the $ node app command. You will see Server running on port 8082. You can also check it from the browser by opening the browser and entering http://localhost:8082.

At this point, if we change anything, we’ll need to restart the server manually. But, if we set up nodemon, then we don’t have to restart it every time. Nodemon will watch if there is any change and restart the server automatically.

So, what you need to do for that is a little change to the scripts in our package.json file. See below:

// package.json

{
  "name": "mern_a_to_z",
  "version": "1.0.0",
  "description": "",
  "main": "app.js",
  "scripts": {
    "start": "node app.js",
    "app": "nodemon app.js",
    "test": "echo \"Error: no test specified\" && exit 1"
  },
  "repository": {
    "type": "git",
    "url": "git+https://github.com/nurislam03/MERN_A_to_Z.git"
  },
  "author": "Nur Islam",
  "license": "MIT",
  "bugs": {
    "url": "https://github.com/nurislam03/MERN_A_to_Z/issues"
  },
  "homepage": "https://github.com/nurislam03/MERN_A_to_Z#readme",
  "dependencies": {
    "body-parser": "^1.19.0",
    "express": "^4.17.1",
    "mongoose": "^5.5.15",
    "validation": "0.0.1"
  },
  "devDependencies": {
    "nodemon": "^1.19.1"
  }
}

Now, you can run your project using the $ npm run app command. If you get any error at this point, then run the commands below:

$ npm install
$ npm run app

You will see the following changes in your terminal if everything goes right:

MERN Stack Running Project Successfully

Database management with MongoDB

Now, it’s time to work on our MERN database setup with MongoDB. For simplicity, we will use MongoDB Atlas. First, create an account here. After creating an account, you will see something like this:

MongoDB Atlas Home Screen

Click the Project 0 section (top left), and you will see a button for Creating a New Project. Create a project and select the project. Now, click the Build a Cluster button from the project you have created. It will show you all the information.

At the bottom, you will see a section called Cluster Name, click that and enter a name for the database, then hit the Create Cluster button. After two to three minutes, if everything goes well, you will find something like this:

Creating a Cluster in MongoDB Atlas

Click the CONNECT button and fill in the username and password form for your database:

Setting Up Our MongoDB Connection

Now, select the Create MongoDB User button. You can also choose either your current IP address or a different IP address; it’s up to you. Now, if you follow the CONNECT button or the Choose a connection method button, you will see some different methods. Select accordingly:

MERN Stack A-Z

In this case, select the Connect Your Application section. Now, you will get your database link, which we will use in our next step:

MERN Stack A-Z Two

Adding the database to our project

Our database is ready, and we need to add it to our project. Inside the project folder, create another folder named config and create two files named default.json and db.js.

Add the following code:

// default.json

{
  "mongoURI":
    "mongodb+srv://mern123:<password>@mernatoz-9kdpd.mongodb.net/test?retryWrites=true&w=majority"
}
 /* Replace <password> with your database password */
// db.js

const mongoose = require('mongoose');
const config = require('config');
const db = config.get('mongoURI');

const connectDB = async () => {
  try {
    mongoose.set('strictQuery', true);
    await mongoose.connect(db, {
      useNewUrlParser: true,
    });

    console.log('MongoDB is Connected...');
  } catch (err) {
    console.error(err.message);
    process.exit(1);
  }
};

module.exports = connectDB;

We need a little change in our app.js file to connect to the database. Update your app.js with this:

// app.js

const express = require('express');
const connectDB = require('./config/db');

const app = express();

// Connect Database
connectDB();

app.get('/', (req, res) => res.send('Hello world!'));

const port = process.env.PORT || 8082;

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

Now, you can run the project using the $ npm run app command. You should see the following:

MERN Stack Successfully Connected Server

Great! So far, we are on the right track, and our database is successfully connected. Now, time to complete the route setup, and after that, we will see how to create RESTful APIs.

Building RESTful APIs with the MERN stack

To get started, create a folder named routes. In it, create another folder named api, which will hold all our APIs. Inside the api folder, create a file named books.js. We will create some APIs here to show how it works in a moment.



Now, update your books.js with the following code:

// routes/api/books.js

const express = require('express');
const router = express.Router();

// Load Book model
const Book = require('../../models/Books');

// @route GET api/books/test
// @description tests books route
// @access Public
router.get('/test', (req, res) => res.send('book route testing!'));

// @route GET api/books
// @description Get all books
// @access Public
router.get('/', (req, res) => {
  Book.find()
    .then(books => res.json(books))
    .catch(err => res.status(404).json({ nobooksfound: 'No Books found' }));
});

// @route GET api/books/:id
// @description Get single book by id
// @access Public
router.get('/:id', (req, res) => {
  Book.findById(req.params.id)
    .then(book => res.json(book))
    .catch(err => res.status(404).json({ nobookfound: 'No Book found' }));
});

// @route GET api/books
// @description add/save book
// @access Public
router.post('/', (req, res) => {
  Book.create(req.body)
    .then(book => res.json({ msg: 'Book added successfully' }))
    .catch(err => res.status(400).json({ error: 'Unable to add this book' }));
});

// @route GET api/books/:id
// @description Update book
// @access Public
router.put('/:id', (req, res) => {
  Book.findByIdAndUpdate(req.params.id, req.body)
    .then(book => res.json({ msg: 'Updated successfully' }))
    .catch(err =>
      res.status(400).json({ error: 'Unable to update the Database' })
    );
});

// @route GET api/books/:id
// @description Delete book by id
// @access Public
router.delete('/:id', (req, res) => {
  Book.findByIdAndRemove(req.params.id, req.body)
    .then(book => res.json({ mgs: 'Book entry deleted successfully' }))
    .catch(err => res.status(404).json({ error: 'No such a book' }));
});

module.exports = router;

Database model

In order to interact with our database, we need to create a model for each of our resources. So, create a folder called models in the root and inside the models folder, create a file called Book.js and update it with this:

// models/Book.js

const mongoose = require('mongoose');

const BookSchema = new mongoose.Schema({
  title: {
    type: String,
    required: true
  },
  isbn: {
    type: String,
    required: true
  },
  author: {
    type: String,
    required: true
  },
  description: {
    type: String
  },
  published_date: {
    type: Date
  },
  publisher: {
    type: String
  },
  updated_date: {
    type: Date,
    default: Date.now
  }
});

module.exports = Book = mongoose.model('book', BookSchema);

Next, run the project to see if everything is fine at this point, and you can test all the APIs through Postman. It’s important to note that before testing APIs using Postman, you need to run the project first.

Authentication and authorization

Authentication and authorization are important parts of web applications. Authentication is the process of verifying someone who says who they are. And authorization is the method of checking the user’s access level.

When using a Node.js backend and a React frontend, most of the time, developers tend to use JWT as an authentication method. JWT stands for JSON Web Tokens. JWTs are encoded and URL-safe strings that can hold an unlimited amount of data.

In most cases, a React fronted sends a JWT to the backend. The backend verifies the token, and if the token is valid, the backend performs the required operations. Check out the video below to learn more about implementing authentication to your application:

Building the frontend

So far, so good! Now that we’ve set up our backend, it’s time to transition to the frontend part of this MERN stack tutorial. In this section, we’ll use React to build our user interfaces. We’ll use Create React App to generate our initial file setup.

We’ll also use webpack and Babel to bundle our modules and compile our JavaScript, respectively. If you don’t know webpack or Babel well, no problem; you don’t need to install or configure tools like webpack or Babel. They’re pre-configured and hidden so that you can focus on the code.

Just create a project, and you’re good to go. You’ll also need any version of Node.js greater than v8.10 and any version of npm greater than v5.6 installed on your local development machine.

Setting up Create React App

Set any directory using a terminal where you want to keep all the files of this project and run $ npx create-react-app my-app to get the initial setup file.

You can replace my-app with whatever you’d like to use as your project name. For example, my project name is mern_a_to_z_client, and my command is $ npx create-react-app mern_a_to_z_client.

Note: The project name must be in lowercase letters. If everything goes right, then you will see something like the following image, where you will find some instructions along with the commands:

MERN Stack Project Successfully Created in Create React App

Before using any built-in command, we need to go inside the project folder with $ cd mern_a_to_z_client.

Now that we are in the project directory, we can use those available commands. If you’re using Yarn, enter $ yarn start. If you’re using npm use $ npm start.

To run the app in development mode, you can use any of the above commands, and you will see the following message in your terminal:

MERN Stack Running

Now, open http://localhost:3000 to view it in the browser. This page will automatically reload if you make changes to the code:

React App Browser

Inside the project directory, our initial file structure should look like this:

MERN Stack Folder

Adding Bootstrap and Font Awesome to your React app

We have got our initial setup file for the frontend part. Now, we can start integrating our backend with our frontend. Before that, I want to add Bootstrap and Font Awesome’s CDN to our project.

Open the file called index.html, which is in the public folder mern_a_to_z_client/public/index.html, and replace everything with the following code:

<!DOCTYPE html>
<html lang="en">
  <head>
    <meta charset="utf-8" />
    <link rel="shortcut icon" href="%PUBLIC_URL%/favicon.ico" />
    <meta name="viewport" content="width=device-width, initial-scale=1" />
    <meta name="theme-color" content="#000000" />
    <!--
      manifest.json provides metadata used when your web app is installed on a
      user's mobile device or desktop. See https://developers.google.com/web/fundamentals/web-app-manifest/
    -->
    <link rel="manifest" href="%PUBLIC_URL%/manifest.json" />
    <!--
      Notice the use of %PUBLIC_URL% in the tags above.
      It will be replaced with the URL of the `public` folder during the build.
      Only files inside the `public` folder can be referenced from the HTML.

      Unlike "/favicon.ico" or "favicon.ico", "%PUBLIC_URL%/favicon.ico" will
      work correctly both with client-side routing and a non-root public URL.
      Learn how to configure a non-root public URL by running `npm run build`.
    -->

    <!-- bootstrap css cdn -->
    <link rel="stylesheet" href="https://maxcdn.bootstrapcdn.com/bootstrap/4.0.0/css/bootstrap.min.css" integrity="sha384-Gn5384xqQ1aoWXA+058RXPxPg6fy4IWvTNh0E263XmFcJlSAwiGgFAW/dAiS6JXm" crossorigin="anonymous">

    <!-- fontawesome cdn -->
    <link rel="stylesheet" href="https://use.fontawesome.com/releases/v5.2.0/css/all.css" integrity="sha384-hWVjflwFxL6sNzntih27bfxkr27PmbbK/iSvJ+a4+0owXq79v+lsFkW54bOGbiDQ" crossorigin="anonymous">

    <title>MERN A to Z</title>
  </head>
  <body>
    <noscript>You need to enable JavaScript to run this app.</noscript>
    <div id="root"></div>
    <!--
      This HTML file is a template.
      If you open it directly in the browser, you will see an empty page.

      You can add webfonts, meta tags, or analytics to this file.
      The build step will place the bundled scripts into the <body> tag.

      To begin the development, run `npm start` or `yarn start`.
      To create a production bundle, use `npm run build` or `yarn build`.
    -->

    <!-- bootstrap JS cdn -->
    <script src="https://code.jquery.com/jquery-3.2.1.slim.min.js" integrity="sha384-KJ3o2DKtIkvYIK3UENzmM7KCkRr/rE9/Qpg6aAZGJwFDMVNA/GpGFF93hXpG5KkN" crossorigin="anonymous"></script>
    <script src="https://cdnjs.cloudflare.com/ajax/libs/popper.js/1.12.9/umd/popper.min.js" integrity="sha384-ApNbgh9B+Y1QKtv3Rn7W3mgPxhU9K/ScQsAP7hUibX39j7fakFPskvXusvfa0b4Q" crossorigin="anonymous"></script>
    <script src="https://maxcdn.bootstrapcdn.com/bootstrap/4.0.0/js/bootstrap.min.js" integrity="sha384-JZR6Spejh4U02d8jOt6vLEHfe/JQGiRRSQQxSfFWpi1MquVdAyjUar5+76PVCmYl" crossorigin="anonymous"></script>

  </body>
</html>

Our frontend will have the following features:

  1. Add, create, or save a new book
  2. Show all the books we have stored in the database
  3. Show a single book
  4. Update a book
  5. Delete a book

Now, use the following command to add some necessary dependencies:

$ npm install --save react-router-dom
$ npm install --save axios

Why Axios?

Axios is a lightweight HTTP client for Node.js and the browser, similar to a Fetch API. Axios is a promise-based async/await library for readable asynchronous code. We can easily integrate it with React, and it is effortless to use in any frontend framework. We’ll call our APIs through Axios.

There are multiple reasons why Axios is widely used. One of the biggest benefits of Axios is its backward compatibility. Old browsers like IE11 can also easily run Axios as the package issues the XMLHttpRequest under the hood.

Axios also automatically stringifies the payload when sending a request. But, when you are using Fetch API, it is important that you convert the payload to JSON.

The Package.json file

At this point, our package.json file should be similar (but can be different) to the code below:

// MERN_A_to_Z_Client - package.json

{
  "name": "mern_a_to_z_client",
  "version": "0.1.0",
  "private": true,
  "dependencies": {
    "@testing-library/jest-dom": "^5.16.5",
    "@testing-library/react": "^13.4.0",
    "@testing-library/user-event": "^13.5.0",
    "axios": "^1.2.1",
    "react": "^18.2.0",
    "react-dom": "^18.2.0",
    "react-router-dom": "^6.4.5",
    "react-scripts": "5.0.1",
    "web-vitals": "^2.1.4"
  },
  "scripts": {
    "start": "react-scripts start",
    "build": "react-scripts build",
    "test": "react-scripts test",
    "eject": "react-scripts eject"
  },
  "eslintConfig": {
    "extends": [
      "react-app",
      "react-app/jest"
    ]
  },
  "browserslist": {
    "production": [
      ">0.2%",
      "not dead",
      "not op_mini all"
    ],
    "development": [
      "last 1 chrome version",
      "last 1 firefox version",
      "last 1 safari version"
    ]
  }
}

Creating the component file

Inside the src folder (mern_a_to_z_client/src/), create another folder called components, and inside it, create five different files:

  1. CreateBook.js
  2. ShowBookList.js
  3. BookCard.js
  4. ShowBookDetails.js
  5. UpdateBookInfo.js

We will work with these five files a bit later.

Functional components and React Hooks

React functional components are a comparatively newer addition to the framework. Earlier, we only had class-based components. A functional component in React is essentially a JavaScript function that returns the React element or JSX. A functional component can be written using the conventional keyword or arrow function. Props are based on function arguments. A functional component can be used on other files only after exporting them:

function Hello(props) {
  return <h1>Hello, {props.name}</h1>;
}

export default Welcome;

The function shown above is a valid React functional component. In this tutorial, we will be using React functional components. To dive deeper into React functional components, check out this article to learn more.

Before React v16.8, developers were only allowed to manage state, and other React features in class-based components. In React v16.8, the React team introduced Hooks. Hooks allow the developers to manage code and other React features inside a functional component.

Check out our cheat sheet for more information on React Hooks.

Setting up routes

Open the App.js folder inside the src folder (mern_a_to_z_client/src/App.js), and replace it with the following code:

import { BrowserRouter as Router, Route, Routes } from 'react-router-dom';
import './App.css';

import CreateBook from './components/CreateBook';
import ShowBookList from './components/ShowBookList';
import ShowBookDetails from './components/ShowBookDetails';
import UpdateBookInfo from './components/UpdateBookInfo';

const App = () => {
  return (
    <Router>
      <div>
        <Routes>
          <Route exact path='/' element={<ShowBookList />} />
          <Route path='/create-book' element={<CreateBook />} />
          <Route path='/edit-book/:id' element={<UpdateBookInfo />} />
          <Route path='/show-book/:id' element={<ShowBookDetails />} />
        </Routes>
      </div>
    </Router>
  );
};

export default App;

Here, we define all the routes. For a specific path definition, its corresponding component will be rendered. We have not implemented these files and components yet — we’ve just completed the path setup.

Updating the CSS file

Next, update a CSS file called App.css in the src folder with the following code:

.App {
  text-align: center;
}

.App-logo {
  animation: App-logo-spin infinite 20s linear;
  height: 40vmin;
  pointer-events: none;
}

.App-header {
  background-color: #282c34;
  min-height: 100vh;
  display: flex;
  flex-direction: column;
  align-items: center;
  justify-content: center;
  font-size: calc(10px + 2vmin);
  color: white;
}

.App-link {
  color: #61dafb;
}

@keyframes App-logo-spin {
  from {
    transform: rotate(0deg);
  }
  to {
    transform: rotate(360deg);
  }
}

.CreateBook {
  background-color: #2c3e50;
  min-height: 100vh;
  color: white;
}

.ShowBookDetails {
  background-color: #2c3e50;
  min-height: 100vh;
  color: white;
}

.UpdateBookInfo {
  background-color: #2c3e50;
  min-height: 100vh;
  color: white;
}

.ShowBookList {
  background-color: #2c3e50;
  height: 100%;
  width: 100%;
  min-height: 100vh;
  min-width: 100px;
  color: white;
}

/* BookList Styles */
.list {
  display: grid;
  margin: 20px 0 50px 0;
  grid-template-columns: repeat(4, 1fr);
  grid-auto-rows: 1fr;
  grid-gap: 2em;
}

.card-container {
  width: 250px;
  border: 1px solid rgba(0,0,.125);
  margin: 0 auto;
  border-radius: 5px;
  overflow: hidden;
}

.desc {
  height: 130px;
  padding: 10px;
}

.desc h2 {
  font-size: 1em;
  font-weight: 400;
}

.desc h3, p {
  font-weight: 300;
}

.desc h3 {
  color: #6c757d;
  font-size: 1em;
  padding: 10px 0 10px 0;
}

Adding our feature components

Now, it’s time to add feature components to our MERN stack project. Our CreateBook.js file is responsible for adding, creating, or saving a new book or a book’s info.

CreateBook.js

So, update CreateBook.js with the following code:

import React, { useState } from 'react';
import { Link } from 'react-router-dom';
import axios from 'axios';

import { useNavigate } from 'react-router-dom';

const CreateBook = (props) => {
  // Define the state with useState hook
  const navigate = useNavigate();
  const [book, setBook] = useState({
    title: '',
    isbn: '',
    author: '',
    description: '',
    published_date: '',
    publisher: '',
  });

  const onChange = (e) => {
    setBook({ ...book, [e.target.name]: e.target.value });
  };

  const onSubmit = (e) => {
    e.preventDefault();

    axios
      .post('http://localhost:8082/api/books', book)
      .then((res) => {
        setBook({
          title: '',
          isbn: '',
          author: '',
          description: '',
          published_date: '',
          publisher: '',
        });

        // Push to /
        navigate('/');
      })
      .catch((err) => {
        console.log('Error in CreateBook!');
      });
  };

  return (
    <div className='CreateBook'>
      <div className='container'>
        <div className='row'>
          <div className='col-md-8 m-auto'>
            <br />
            <Link to='/' className='btn btn-outline-warning float-left'>
              Show BooK List
            </Link>
          </div>
          <div className='col-md-8 m-auto'>
            <h1 className='display-4 text-center'>Add Book</h1>
            <p className='lead text-center'>Create new book</p>

            <form noValidate onSubmit={onSubmit}>
              <div className='form-group'>
                <input
                  type='text'
                  placeholder='Title of the Book'
                  name='title'
                  className='form-control'
                  value={book.title}
                  onChange={onChange}
                />
              </div>
              <br />

              <div className='form-group'>
                <input
                  type='text'
                  placeholder='ISBN'
                  name='isbn'
                  className='form-control'
                  value={book.isbn}
                  onChange={onChange}
                />
              </div>

              <div className='form-group'>
                <input
                  type='text'
                  placeholder='Author'
                  name='author'
                  className='form-control'
                  value={book.author}
                  onChange={onChange}
                />
              </div>

              <div className='form-group'>
                <input
                  type='text'
                  placeholder='Describe this book'
                  name='description'
                  className='form-control'
                  value={book.description}
                  onChange={onChange}
                />
              </div>

              <div className='form-group'>
                <input
                  type='date'
                  placeholder='published_date'
                  name='published_date'
                  className='form-control'
                  value={book.published_date}
                  onChange={onChange}
                />
              </div>
              <div className='form-group'>
                <input
                  type='text'
                  placeholder='Publisher of this Book'
                  name='publisher'
                  className='form-control'
                  value={book.publisher}
                  onChange={onChange}
                />
              </div>

              <input
                type='submit'
                className='btn btn-outline-warning btn-block mt-4'
              />
            </form>
          </div>
        </div>
      </div>
    </div>
  );
};

export default CreateBook;

ShowBookList.js

The ShowBookList.js component will be responsible for showing all the books we already have stored in our database. Update ShowBookList.js with this code:

import React, { useState, useEffect } from 'react';
import '../App.css';
import axios from 'axios';
import { Link } from 'react-router-dom';
import BookCard from './BookCard';

function ShowBookList() {
  const [books, setBooks] = useState([]);

  useEffect(() => {
    axios
      .get('http://localhost:8082/api/books')
      .then((res) => {
        setBooks(res.data);
      })
      .catch((err) => {
        console.log('Error from ShowBookList');
      });
  }, []);

  const bookList =
    books.length === 0
      ? 'there is no book record!'
      : books.map((book, k) => <BookCard book={book} key={k} />);

  return (
    <div className='ShowBookList'>
      <div className='container'>
        <div className='row'>
          <div className='col-md-12'>
            <br />
            <h2 className='display-4 text-center'>Books List</h2>
          </div>

          <div className='col-md-11'>
            <Link
              to='/create-book'
              className='btn btn-outline-warning float-right'
            >
              + Add New Book
            </Link>
            <br />
            <br />
            <hr />
          </div>
        </div>

        <div className='list'>{bookList}</div>
      </div>
    </div>
  );
}

export default ShowBookList;

BookCard.js

Here, we use a functional component called BookCard.js, which takes a book’s info from ShowBookList.js and makes a card for each book. Write the following code to update your BookCard.js file:

import React from 'react';
import { Link } from 'react-router-dom';
import '../App.css';

const BookCard = (props) => {
  const book = props.book;

  return (
    <div className='card-container'>
      <img
        src='https://images.unsplash.com/photo-1495446815901-a7297e633e8d'
        alt='Books'
        height={200}
      />
      <div className='desc'>
        <h2>
          <Link to={`/show-book/${book._id}`}>{book.title}</Link>
        </h2>
        <h3>{book.author}</h3>
        <p>{book.description}</p>
      </div>
    </div>
  );
};

export default BookCard;

NOTE: Here, I used the same img src for each book, since each book’s respective image may not always be available. Change the image source, and you can also use a different image for each book.

The ShowBookDetails component has one task: it shows all the info we have about any book. We have both delete and edit buttons here to get access:

import React, { useState, useEffect } from 'react';
import { Link, useParams, useNavigate } from 'react-router-dom';
import '../App.css';
import axios from 'axios';

function ShowBookDetails(props) {
  const [book, setBook] = useState({});

  const { id } = useParams();
  const navigate = useNavigate();

  useEffect(() => {
    axios
      .get(`http://localhost:8082/api/books/${id}`)
      .then((res) => {
        setBook(res.data);
      })
      .catch((err) => {
        console.log('Error from ShowBookDetails');
      });
  }, [id]);

  const onDeleteClick = (id) => {
    axios
      .delete(`http://localhost:8082/api/books/${id}`)
      .then((res) => {
        navigate('/');
      })
      .catch((err) => {
        console.log('Error form ShowBookDetails_deleteClick');
      });
  };

  const BookItem = (
    <div>
      <table className='table table-hover table-dark'>
        <tbody>
          <tr>
            <th scope='row'>1</th>
            <td>Title</td>
            <td>{book.title}</td>
          </tr>
          <tr>
            <th scope='row'>2</th>
            <td>Author</td>
            <td>{book.author}</td>
          </tr>
          <tr>
            <th scope='row'>3</th>
            <td>ISBN</td>
            <td>{book.isbn}</td>
          </tr>
          <tr>
            <th scope='row'>4</th>
            <td>Publisher</td>
            <td>{book.publisher}</td>
          </tr>
          <tr>
            <th scope='row'>5</th>
            <td>Published Date</td>
            <td>{book.published_date}</td>
          </tr>
          <tr>
            <th scope='row'>6</th>
            <td>Description</td>
            <td>{book.description}</td>
          </tr>
        </tbody>
      </table>
    </div>
  );

  return (
    <div className='ShowBookDetails'>
      <div className='container'>
        <div className='row'>
          <div className='col-md-10 m-auto'>
            <br /> <br />
            <Link to='/' className='btn btn-outline-warning float-left'>
              Show Book List
            </Link>
          </div>
          <br />
          <div className='col-md-8 m-auto'>
            <h1 className='display-4 text-center'>Book's Record</h1>
            <p className='lead text-center'>View Book's Info</p>
            <hr /> <br />
          </div>
          <div className='col-md-10 m-auto'>{BookItem}</div>
          <div className='col-md-6 m-auto'>
            <button
              type='button'
              className='btn btn-outline-danger btn-lg btn-block'
              onClick={() => {
                onDeleteClick(book._id);
              }}
            >
              Delete Book
            </button>
          </div>
          <div className='col-md-6 m-auto'>
            <Link
              to={`/edit-book/${book._id}`}
              className='btn btn-outline-info btn-lg btn-block'
            >
              Edit Book
            </Link>
          </div>
        </div>
      </div>
    </div>
  );
}

export default ShowBookDetails;

UpdateBookInfo.js

UpdateBookInfo.js, as its name indicates, is responsible for updating a book’s info. An Edit Book button will trigger this component to perform. After clicking Edit Book, we will see a form with the old info, which we will be able to edit or replace:

import React, { useState, useEffect } from 'react';
import { Link, useParams, useNavigate } from 'react-router-dom';
import axios from 'axios';
import '../App.css';

function UpdateBookInfo(props) {
  const [book, setBook] = useState({
    title: '',
    isbn: '',
    author: '',
    description: '',
    published_date: '',
    publisher: '',
  });

  const { id } = useParams();
  const navigate = useNavigate();

  useEffect(() => {
    axios
      .get(`http://localhost:8082/api/books/${id}`)
      .then((res) => {
        setBook({
          title: res.data.title,
          isbn: res.data.isbn,
          author: res.data.author,
          description: res.data.description,
          published_date: res.data.published_date,
          publisher: res.data.publisher,
        });
      })
      .catch((err) => {
        console.log('Error from UpdateBookInfo');
      });
  }, [id]);

  const onChange = (e) => {
    setBook({ ...book, [e.target.name]: e.target.value });
  };

  const onSubmit = (e) => {
    e.preventDefault();

    const data = {
      title: book.title,
      isbn: book.isbn,
      author: book.author,
      description: book.description,
      published_date: book.published_date,
      publisher: book.publisher,
    };

    axios
      .put(`http://localhost:8082/api/books/${id}`, data)
      .then((res) => {
        navigate(`/show-book/${id}`);
      })
      .catch((err) => {
        console.log('Error in UpdateBookInfo!');
      });
  };

  return (
    <div className='UpdateBookInfo'>
      <div className='container'>
        <div className='row'>
          <div className='col-md-8 m-auto'>
            <br />
            <Link to='/' className='btn btn-outline-warning float-left'>
              Show BooK List
            </Link>
          </div>
          <div className='col-md-8 m-auto'>
            <h1 className='display-4 text-center'>Edit Book</h1>
            <p className='lead text-center'>Update Book's Info</p>
          </div>
        </div>

        <div className='col-md-8 m-auto'>
          <form noValidate onSubmit={onSubmit}>
            <div className='form-group'>
              <label htmlFor='title'>Title</label>
              <input
                type='text'
                placeholder='Title of the Book'
                name='title'
                className='form-control'
                value={book.title}
                onChange={onChange}
              />
            </div>
            <br />

            <div className='form-group'>
              <label htmlFor='isbn'>ISBN</label>
              <input
                type='text'
                placeholder='ISBN'
                name='isbn'
                className='form-control'
                value={book.isbn}
                onChange={onChange}
              />
            </div>
            <br />

            <div className='form-group'>
              <label htmlFor='author'>Author</label>
              <input
                type='text'
                placeholder='Author'
                name='author'
                className='form-control'
                value={book.author}
                onChange={onChange}
              />
            </div>
            <br />

            <div className='form-group'>
              <label htmlFor='description'>Description</label>
              <textarea
                type='text'
                placeholder='Description of the Book'
                name='description'
                className='form-control'
                value={book.description}
                onChange={onChange}
              />
            </div>
            <br />

            <div className='form-group'>
              <label htmlFor='published_date'>Published Date</label>
              <input
                type='text'
                placeholder='Published Date'
                name='published_date'
                className='form-control'
                value={book.published_date}
                onChange={onChange}
              />
            </div>
            <br />

            <div className='form-group'>
              <label htmlFor='publisher'>Publisher</label>
              <input
                type='text'
                placeholder='Publisher of the Book'
                name='publisher'
                className='form-control'
                value={book.publisher}
                onChange={onChange}
              />
            </div>
            <br />

            <button
              type='submit'
              className='btn btn-outline-info btn-lg btn-block'
            >
              Update Book
            </button>
          </form>
        </div>
      </div>
    </div>
  );
}

export default UpdateBookInfo;

Connecting and running the frontend to the backend

We just implemented all of our components! Now, we need a little change in our server side backend project.

If we try to call our backend API from the frontend part, it gets an error:

"Access to XMLHttpRequest at 'http://localhost:8082/api/books' from origin 'http://localhost:3000' has been blocked by CORS policy: Response to preflight request doesn't pass access control check: No 'Access-Control-Allow-Origin' header is present on the requested resource."

To solve this, we need to install cors in our backend server side project. Go to the project folder and run $ npm install cors.

Now, update app.js, the backend’s entry point with the following code:

// app.js

const express = require('express');
const connectDB = require('./config/db');
const cors = require('cors');

// routes
const books = require('./routes/api/books');

const app = express();

// Connect Database
connectDB();

// cors
app.use(cors({ origin: true, credentials: true }));

// Init Middleware
app.use(express.json({ extended: false }));

app.get('/', (req, res) => res.send('Hello world!'));

// use Routes
app.use('/api/books', books);

const port = process.env.PORT || 8082;

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

It is also important that you add this line, app.use(express.json({ extended: false }));. The express.json method allows Express to read data sent using a POST or PUT request. It is used for recognizing incoming objects as JSON objects.

Running the frontend and backend

Follow the steps below to run both the frontend and backend of our MERN stack example.

First, run the server (inside the project folder):

$ npm run app

If you get any errors, then follow the commands below (inside the project folder):

$ npm install
$ npm run app

To run the client, run the command below from the frontend project directory:

$ npm start

If you get an error again, follow the same commands below:

$ npm install
$ npm start

Testing our MERN stack app in the browser

Let’s check everything in the browser. Open http://localhost:3000 in your browser. Now, you can add a book, delete a book, show the list of books, and edit books. The following routes should perform accordingly:

Add a new book:

http://localhost:3000/create-book

MERN Stack Books One

Show the list of books:

http://localhost:3000/

MERN Stack Books Two

Show any book’s info:

http://localhost:3000/show-book/:id

MERN Stack Books Three

Update a book’s info:

http://localhost:3000/edit-book/:id

MERN Stack Books Four

Conclusion

Congratulations! You have successfully completed this MERN stack tutorial. You can visit my GitHub to see the server side and client side portions of this MERN stack tutorial. You can also find the complete repo for our MERN stack example app here.

LogRocket: Full visibility into your production React apps

Debugging React applications can be difficult, especially when users experience issues that are hard to reproduce. If you’re interested in monitoring and tracking Redux state, automatically surfacing JavaScript errors, and tracking slow network requests and component load time, try LogRocket.

LogRocket combines session replay, product analytics, and error tracking – empowering software teams to create the ideal web and mobile product experience. What does that mean for you?

Instead of guessing why errors happen, or asking users for screenshots and log dumps, LogRocket lets you replay problems as if they happened in your own browser to quickly understand what went wrong.

No more noisy alerting. Smart error tracking lets you triage and categorize issues, then learns from this. Get notified of impactful user issues, not false positives. Less alerts, way more useful signal.

The LogRocket Redux middleware package adds an extra layer of visibility into your user sessions. LogRocket logs all actions and state from your Redux stores.

Modernize how you debug your React apps — .

Nur Islam Sport programmer.

37 Replies to “The MERN stack tutorial”

  1. Great tutorial, but isn’t the line
    ‘app.use(‘/api/books’, books);
    missing from app.js if we want to test our routes with Postman/Insomnia?

  2. to be able to test the api routes, you will need to add

    //in app.js add below
    const routes = require('./routes/books');

    app.use('/api', routes);

  3. Hi, i’m getting the error below when trying to connect:

    failed to connect to server [cluster0-shard-00-00-ehcci.mongodb.net:27017] on first connect [MongoNetworkError: connect ECONNREFUSED 3.227.163.176:27017]

    although i have my IP whitlisted and even switched to accepting request from any IP but still getting this error

  4. When I connect PC’s developing((192.168.1.116:3000 / localhost:3000) to MongoDB Atlas, that’s OK I can add update DB. So I try to connect other PC same LAN network to PC’s developing URL:192.168.1.116:3000 I saw the WEB page but I cannot get Book list , I cannot ADD book.

    How can I solve the problem?

  5. Hello, I´d like to ask, what can I do, if i have this error:

    TypeError: connectDB is not a function

    at Object. (/mnt/c/Users/Michael/Desktop/WebApps/mern_stack/app.js:7:1)
    at Module._compile (internal/modules/cjs/loader.js:955:30)
    at Object.Module._extensions..js (internal/modules/cjs/loader.js:991:10)
    at Module.load (internal/modules/cjs/loader.js:811:32)
    at Function.Module._load (internal/modules/cjs/loader.js:723:14)
    at Function.Module.runMain (internal/modules/cjs/loader.js:1043:10)
    at internal/main/run_main_module.js:17:11
    [nodemon] app crashed – waiting for file changes before starting…
    [nodemon] restarting due to changes…
    [nodemon] starting `node app.js`
    /mnt/c/Users/Michael/Desktop/WebApps/mern_stack/app.js:7
    connectDB();
    ^

    [nodemon] starting `node app.js`
    /mnt/c/Users/Michael/Desktop/WebApps/mern_stack/app.js:7
    connectDB();
    ^

    Thank you for your response.

    1. Copying and pasting my comment to Aumkar:

      I believe the issue may be that you don’t have ‘module.exports = connectDB;’ at the bottom of the file. So app.js is not actually importing the specific connectDB function you wrote.

  6. There’s a bug in the db.js instructions. The word “parser” should be capitalized in: useNewUrlparser: true

  7. This is a great tutorial! Got one more issue: With just the code in Part 1, it’s not possible to use Postman to test the APIs. To make it work, app.js needs to be updated to include:

    const books = require(‘./routes/api/books’);
    app.use(‘/api/books’, books);

    That will wire up the APIs so they can be tested.

  8. Can someone help me? I’m getting the same error as @MichaelRydl, where I am able to connect to the DB. I replaced the url in the default.json with the url on the ATLAS, with the username and password that I set for the user, however am still facing this error.

  9. I believe the issue may be that you don’t have ‘module.exports = connectDB;’ at the bottom of the file. So app.js is not actually importing the specific connectDB function you wrote.

  10. I had to initialize the body-parser in order to get the server to work.

    var bodyParser = require(‘body-parser’)

    // parse application/json
    app.use(bodyParser.json())

  11. If you encounter the following error/warning when running the react app:
    “Warning: Functions are not valid as a React child.This may happen if you return a Component instead of from render.”

    The error is located in App.js.
    The solution to fix this issue is below, pay special attention to how I wrapped each element in :

    class App extends Component {
    render() {
    return (

    <Route exact path='/' element={} />
    <Route path='/create-book' element={} />
    <Route path='/edit-book/:id' element={} />
    <Route path='/show-book/:id' element={} />

    );
    }
    }

  12. Can someone help? Great tutorial, but for some reason when I update the app.js file with the recommended code, my database crashes when I try to run it with the following console message. The db runs without any errors before this step. Any suggestions?

    Error: Cannot find module ‘./routes/api/books’
    Require stack:
    – /Users/michaelbmagruder/Documents/PROJECTS/MERN/project001/app.js
    at Function.Module._resolveFilename (internal/modules/cjs/loader.js:889:15)
    at Function.Module._load (internal/modules/cjs/loader.js:745:27)
    at Module.require (internal/modules/cjs/loader.js:961:19)
    at require (internal/modules/cjs/helpers.js:92:18)
    at Object. (/Users/michaelbmagruder/Documents/PROJECTS/MERN/project001/app.js:24:15)
    at Module._compile (internal/modules/cjs/loader.js:1072:14)
    at Object.Module._extensions..js (internal/modules/cjs/loader.js:1101:10)
    at Module.load (internal/modules/cjs/loader.js:937:32)
    at Function.Module._load (internal/modules/cjs/loader.js:778:12)
    at Function.executeUserEntryPoint [as runMain] (internal/modules/run_main.js:76:12) {
    code: ‘MODULE_NOT_FOUND’,
    requireStack: [
    ‘/Users/michaelbmagruder/Documents/PROJECTS/MERN/project001/app.js’
    ]
    }
    [nodemon] app crashed – waiting for file changes before starting…

    1. If you go to line 5 in “./routes/api/books.js” the module needs to be pluralized. The code has “/Book” when it should be “Books”.

  13. Hey there! What if my package.json is missing a couple of the dependencies listed in this project? I went through everything step by step but it seems your package.json has a lot more scripts and dependencies than mine…

  14. Has anyone tried using functional components and hooks? My apps front end wont work because I’m not using functional components

    1. The react-router-dom version 6 syntax is a bit different than what’s shown. You should update your App.js file as follows:

      import React, { Component } from ‘react’;
      import { BrowserRouter, Routes,Route }
      from “react-router-dom”;
      import ‘./App.css’;
      import CreateBook from “./components/CreateBook”;
      import ShowBookDetails from ‘./components/ShowBookDetails’;
      import ShowBookList from ‘./components/ShowBookList’;
      import UpdateBookInfo from ‘./components/UpdateBookInfo’;

      class App extends Component {
      render() {
      return (

      <Route exact path='/' element={} />
      <Route path='/create-book' element={} />
      <Route path='/edit-book/:id' element={} />
      <Route path='/show-book/:id' element={} />

      );
      }
      }

      export default App;

      1. Sorry about the above replay from an earlier version of my code. You should update your App.js file as follows:

        import React, { Component } from ‘react’;
        import { BrowserRouter, Routes,Route }
        from “react-router-dom”;
        import ‘./App.css’;
        import CreateBook from “./components/CreateBook”;
        import ShowBookDetails from ‘./components/ShowBookDetails’;
        import ShowBookList from ‘./components/ShowBookList’;
        import UpdateBookInfo from ‘./components/UpdateBookInfo’;

        class App extends Component {
        render() {
        return (

        <Route exact path='/' element={} />
        <Route path='/create-book' element={} />
        <Route path='/edit-book/:id' element={} />
        <Route path='/show-book/:id' element={} />

        );
        }
        }

        export default App;

        1. I solved this by going into my project directory (server folder) and running:
          npm i react-router-dom
          npm install

          And doing the same in my books-app (or client folder) and running the same thing. This is the part that runs the actual rendering of the page.

    1. Feel like they were going to expand into authorization and user logins to submit entries but stopped short

  15. This blog provides very useful information and knowledge about MERN stack development, Mongo db, and react.js, technologies. It also gives a very clear understanding about how we build, use and utilize of all these technologies

  16. Hello, Experiencing below error after adding db.js and default.json in config folder. Any solution?

    node_modules/mongodb/lib/operations/add_user.js:16
    this.options = options ?? {};
    ^

    SyntaxError: Unexpected token ‘?’
    at wrapSafe (internal/modules/cjs/loader.js:915:16)
    at Module._compile (internal/modules/cjs/loader.js:963:27)
    at Object.Module._extensions..js (internal/modules/cjs/loader.js:1027:10)
    at Module.load (internal/modules/cjs/loader.js:863:32)
    at Function.Module._load (internal/modules/cjs/loader.js:708:14)
    at Module.require (internal/modules/cjs/loader.js:887:19)
    at require (internal/modules/cjs/helpers.js:74:18)
    at Object. (/home/rodgersmm/OneDrive/Akode/MERN Sites/MERN Template/backend/node_modules/mongodb/lib/admin.js:4:20)
    at Module._compile (internal/modules/cjs/loader.js:999:30)
    at Object.Module._extensions..js (internal/modules/cjs/loader.js:1027:10)

Leave a Reply