Create a Simple App using MERN Stack

The MERN stack consists of MongoDB, Express, React, and Node.js. Given the popularity of React on the frontend and of Node.js on the backend, the MERN stack is one of the most popular stack of technologies for building a modern single-page application (SPA).

In this article, we will build a shopping list application that utilizes a RESTful API which we will build in following tutorial. I assume you have little knowledge about javascript, reactjs, and nodejs. In this tutorial, we will be using node and NPM, you need to install them to your computer, you should also have some code editor already installed.

Set-Up

Let’s start with the setup. Open your terminal and create a new file directory in any location in your local machine — 

mkdir shoppinglist

Enter into that file directory

cd shoppinglist

At this stage, we need to initialize our project with a package.json file which will contain some information about our app and the dependencies which our app needs to run. You can use npm init.

Server Setup

To run our javascript code on the backend we need to build a server that will compile our code. We can create our server using Express which is a small node framework to build the REST API.

To make use of express, we need to install it using yarn or NPM.

npm install express

Create a file index.js and type the code below into it and save

const express = require('express');
require('dotenv').config();
const app = express();
const port = process.env.PORT || 5000;
app.use((req, res, next) => {
  res.header("Access-Control-Allow-Origin", "*");
  res.header("Access-Control-Allow-Headers", "Origin, X-Requested-With, Content-Type, Accept");
  next();
});
app.use((req, res, next) => {
  res.send('Welcome to Express Server');
});
app.listen(port, () => {
  console.log(`Server running on port ${port}`)
});

The code below helps us handle CORS related issues we might face if we try to access our API from a different domain.

app.use((req, res, next) => {
  res.header("Access-Control-Allow-Origin", "*");
  res.header("Access-Control-Allow-Headers", "Origin, X-Requested-With, Content-Type, Accept");
  next();
});

It’s time to start our server to see if it works. Open your terminal in the same directory as your index.js file and type

node index.js

If everything goes well, you should see Server running on port 5000 in your terminal.

Routes

We want to achieve three things with our shopping list application, which is to create an item list, view all items and to delete a brought item. For this, we need to create routes that will define various endpoints that the shopping list app will depend on. So let’s create a folder route and create a file API.*js *with the following code in it.

const express = require ('express');
const router = express.Router();
router.get('/item', (req, res, next) => {
});
router.post('/list', (req, res, next) => {
});
router.delete('/list/:id', (req, res, next) => {
})
module.exports = router;

Models

Since our app is going to make use of MongoDB which is a NoSQL database, we need to create a model and a schema. Models are defined using the Schema interface. The Schema allows you to define the fields stored in each document along with their validation requirements and default values. 

To create a Schema and a model we need to install mongoose .

npm install mongoose

Create a new folder in your root directory and name it models, inside it create a file and name it item.js with the following code in it.

const mongoose = require('mongoose');
const Schema = mongoose.Schema;
//create schema for item
const ItemSchema = new Schema({
  action: {
    type: String,
    required: [true, 'The item text field is required']
  }
})
//create model for item
const Item = mongoose.model('item', ItemSchema);
module.exports = Item;

Having created our model we need to update our routes to make use of the new model.

const express = require ('express');
const router = express.Router();
const Item = require('../models/item');
router.get('/list', (req, res, next) => {
  Item.find({}, 'action')
    .then(data => res.json(data))
    .catch(next)
});
router.post('/list', (req, res, next) => {
  if(req.body.action){
    Item.create(req.body)
      .then(data => res.json(data))
      .catch(next)
  }else {
    res.json({
      error: "The input field is empty"
    })
  }
});
router.delete('/list/:id', (req, res, next) => {
  Item.findOneAndDelete({"_id": req.params.id})
    .then(data => res.json(data))
    .catch(next)
})
module.exports = router;

Database

We need a database where we will store our data. For this we will make use of mlab. After setting up your database you need to update index.js file with the following code

const express = require('express');
const bodyParser = require('body-parser');
const mongoose = require('mongoose');
const routes = require('./routes/api');
const path = require('path');
require('dotenv').config();
const app = express();
const port = process.env.PORT || 5000;
//connect to the database
mongoose.connect(process.env.DB, { useNewUrlParser: true })
  .then(() => console.log(`Database connected successfully`))
  .catch(err => console.log(err));
//since mongoose promise is depreciated, we overide it with node's promise
mongoose.Promise = global.Promise;
app.use((req, res, next) => {
  res.header("Access-Control-Allow-Origin", "*");
  res.header("Access-Control-Allow-Headers", "Origin, X-Requested-With, Content-Type, Accept");
  next();
});
app.use(bodyParser.json());
app.use('/api', routes);
app.use((err, req, res, next) => {
  console.log(err);
  next();
});
app.listen(port, () => {
  console.log(`Server running on port ${port}`)
});

In the code above we made use of process.env to access the environment variable which we will create now. Create a file in your root directory with name *.env *and write

DB = ‘mongodb://<USER>:<PASSWORD>@ds039950.mlab.com:39950/item’

Make sure you use your own mongodb url gotten from mlab after creating your database and user. Replace <USER> with the username and <PASSWORD> with the password of the user you created. To work with the environment variable we have to install a node package called dotenv which makes sure we have access to the environment variable stored in the .env file.

npm install dotenv

Then we require and configure it in our index.js file

require('dotenv').config()

The reason why I choose to adopt using an environment variable in our project is that we can hide any sensitive information from our versioning system, so it’s more of security reasons.

Testing Api

This is the part we start trying out things to make sure our RESTful API is working. Since our frontend is not ready yet, we can make use of some API development clients to test our code. I recommend you make use of Postman, if there’s any other client you have, you can still make use of it. Start your server using node index.js then open your client, create a get method and navigate to http://localhost:5000/api/list

You should test all the API endpoints and make sure they are working. For the endpoints that require body, you should send JSON back with the necessary fields since it’s what we setup in our code.

 User Interface

Since we are done with the functionality we want from our api, it’s time to create an interface for the client to interact with the api. To start out with the frontend of the shopping list app, we will use create react app to scaffold our app, so we won’t have to worry ourselves with setting up webpack and babel from scratch. In the same root directory as your backend code, which is the our directory, run create-react-app client

Running the React App

After creating the react app, the next thing is to see if the app is well connected and working as well. But before we test it, we need to perform some actions first.

  • Install concurrently as a dev dependency, using yarn add concurrently --dev or npm install concurrently --save-dev. The advantage of concurrently is that it allows us to run more than one command simultaneously from the same terminal window.
  • Install nodemon as a dev dependency using yarn add nodemon --dev or npm install nodemon --save-dev. We will use nodemon to spin up our server and monitor it as well, so that if there is any change in our server, nodemon will restart it automatically for us.
  • Open your package.json file in the root folder of the mern app project, and paste the following code
paste the following code

{
"name": "Shopping App",
"version": "1.0.0",
"description": "building shopping app using mongodb, express, react and nodejs",
"main": "index.js",
"scripts": {
"start": "node index.js",
"start-watch": "nodemon index.js",
"dev": "concurrently \"yarn run start-watch\" \"cd client && yarn start\""
},
"author": "",
"license": "MIT",
"dependencies": {
"body-parser": "^1.18.3",
"dotenv": "^6.1.0",
"express": "^4.16.4",
"mongoose": "^5.3.6"
},
"devDependencies": {
"concurrently": "^4.0.1",
"nodemon": "^1.18.4"
}
}
  • Enter into the client folder, then locate the package.json file and add the key value pair below inside it. This proxy setup in our package.json file will enable us make api calls without having to type the full url, just /api/list will get list
"proxy": "http://localhost:5000"

Open your terminal and run npm run dev and make sure you are in the app directory and not in the client directory. Voila!, your app should be open and running on localhost:3000.

Creating your Components

One of the beautiful things about react is that it makes use of components, which are reusable and also makes ones code modular. For our shopping list app, we will create two state components and one stateless component. Inside your src folder create another folder called components and inside it create three files Input.js, List.js and Item.js.

Open Input.js file and paste the following

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


class Input extends Component {
  state = {
    action: ""
  }
  addItem= () => {
    const task = {action: this.state.action}
    if(task.action && task.action.length > 0){
      axios.post('/api/list', task)
        .then(res => {
          if(res.data){
            this.props.getList();
            this.setState({action: ""})
          }
        })
        .catch(err => console.log(err))
    }else {
      console.log('input field required')
    }
  }
  handleChange = (e) => {
    this.setState({
      action: e.target.value
    })
  }
  render() {
    let { action } = this.state;
    return (
      <div>
        <input type="text" onChange={this.handleChange} value={action} />
        <button onClick={this.addItem}>add item</button>
      </div>
    )
  }
}
export default Input

After that open your List.js file and paste the following code

import React from 'react';
const List = ({ list, deleteItem }) => {
return (
    <ul>
      {
        list &&
          list.length > 0 ?
            (
              list.map(item => {
                return (
                  <li key={item._id} onClick={() => deleteItem(item._id)}>{item.action}</li>
                )
              })
            )
            :
            (
              <li>No item left</li>
            )
      }
    </ul>
  )
}
export default List

Then in your Item.js file you write the following code

import React, {Component} from 'react';
import axios from 'axios';
import Input from './Input';
import List from './List';
class Item extends Component {
  state = {
    list: []
  }
  componentDidMount(){
    this.getList();
  }
getList = () => {
    axios.get('/api/list')
      .then(res => {
        if(res.data){
          this.setState({
            list: res.data
          })
        }
      })
      .catch(err => console.log(err))
  }
  deleteItem = (id) => {
axios.delete(`/api/list/${id}`)
      .then(res => {
        if(res.data){
          this.getList()
        }
      })
      .catch(err => console.log(err))
  }
  render() {
    let { list } = this.state;
return(
      <div>
        <h1>My Item</h1>
        <Input getList={this.getList}/>
        <List list={list} deleteList={this.deleteList}/>
      </div>
    )
  }
}
export default List;

We need to make little adjustment to our react code, we delete the logo and adjust our App.js to look like this.

import React from 'react';
import Item from './components/Item';
import './App.css';
const App = () => {
  return (
    <div className="App">
      <Item />
    </div>
  );
}
export default App;

Then we paste the following code into our App.css file.

.App {
  text-align: center;
  font-size: calc(10px + 2vmin);
  width: 60%;
  margin-left: auto;
  margin-right: auto;
}
input {
  height: 40px;
  width: 50%;
  border: none;
  border-bottom: 2px #101113 solid;
  background: none;
  font-size: 1.5rem;
  color: #787a80;
}
input:focus {
  outline: none;
}
button {
  width: 25%;
  height: 45px;
  border: none;
  margin-left: 10px;
  font-size: 25px;
  background: #101113;
  border-radius: 5px;
  color: #787a80;
  cursor: pointer;
}
button:focus {
  outline: none;
}
ul {
  list-style: none;
  text-align: left;
  padding: 15px;
  background: #171a1f;
  border-radius: 5px;
}
li {
  padding: 15px;
  font-size: 1.5rem;
  margin-bottom: 15px;
  background: #282c34;
  border-radius: 5px;
  overflow-wrap: break-word;
  cursor: pointer;
}

Conclusion

A journey of a thousand miles begins with a step. I believe you’ve taken that bold step towards learning and understanding the MERN stack. 

Leave a Reply

Your email address will not be published. Required fields are marked *