How to create a simple and beautiful chat with MongoDB, Express, React and Node.js (MERN stack)
Tags: beginners, tutorial, webdev, javascript
Published: 2019-12-30 13:48:35

---
title: How to create a simple and beautiful chat with MongoDB, Express, React and Node.js (MERN stack)
published: true
description: How to create a simple and beautiful chat with MongoDB, Express, React and Node.js (MERN stack)
cover_image: https://thepracticaldev.s3.amazonaws.com/i/2trc2v928sqyuqiumr39.png
tags: beginners, tutorial, webdev, javascript
---

Recently, I worked on an interesting project called [SpeedBoard](https://speedboard.app) which is a real-time board for Agile and Scrum retrospectives. It's the kind of tool we use at work after our Scrum Sprint review to easily share our feedback about the last Sprint.

Since it was a very enriching experience, I thought that I would do a quick tutorial on how to set up a simple chat with the same technology stack which includes: [MongoDB](https://www.mongodb.com/), [Express](https://expressjs.com/), [React](https://reactjs.org/), [Node.js](https://nodejs.org/en/) and is also called the **MERN stack**. I am also using [Socket.IO](https://socket.io/) for the real-time engine and [Material-UI](https://material-ui.com/) which is a UI framework for React based on [Material Design](https://material.io/).

If you don't want to wait until the end of this tutorial, you can already [check a preview of the final result](https://speedchatapp.herokuapp.com/), and also [check the Github repository](https://github.com/armelpingault/speedchatapp) if you want to fork it and start to improve it ;)

# Prerequisites

In this tutorial, we will use [Heroku](https://signup.heroku.com/login) for hosting our live project and [Github](https://github.com/join) for hosting our code and deploying it to Heroku, so make sure you already have an account with them, they both provide a free sign up.

# Structure

Before we start, let's have a quick look at the structure of our project. Inside our root folder, we will have 2 subfolders: one called `client` which contains the React app and one called `server` with our Node.js server:

```
speedchatapp/
├── client/
├── server/
```

Let's open our Terminal and create our project folder:

```
mkdir speedchatapp
cd speedchatapp/
```

# Set up the client

On the client-side, we will use the [Create React App](https://github.com/facebook/create-react-app) (CRA) which provides a very easy way to start building any React SPA.

CRA provides a very simple command to install the app, but first, let's ensure that `npx` is using the latest version if you used `create-react-app` in the past:

```
npm uninstall -g create-react-app
```

Now, let's create our app in our `client` folder with this simple command:

```
npx create-react-app client
```

This might take a couple of minutes to install all the dependencies, and once you are done, try:

```
cd client/
npm start
```

You should now be able to access your app at http://localhost:3000/

![Create React App](https://thepracticaldev.s3.amazonaws.com/i/4rpgohkob3kcftxf1qrf.png)

That was quick and simple : ) But still pretty far from our final result! We'll come back a little bit later to our React app once the server-side of our project is ready.

# Set up the server

Now that we have the skeleton of our `client` ready, let's have a look at the backend side.

First, let's create our `server` folder at the root of our project and initialize our `package.json` file:

```
mkdir server
cd server/
npm init
```

A utility will take you through the configuration of the file but you can type <kbd>Enter</kbd> for all options for this tutorial.

Now, we will install all the dependencies required for our server (Express, Mongoose and Socket.IO) with the following command:

```
npm install express mongoose socket.io --save
```

Then, copy the `.gitignore` file from the `client` folder to the `server` folder to prevent some files and folders to be pushed to our GitHub repository (e.g. `/node_modules` folder):

```
cp ../client/.gitignore ./
```

We will create the 2 files necessary for our server to work. The first one (Message.js) is the schema of the documents we will keep in our database. We will need 3 information: the **name** of the user who is posting a message in the chat, the **content** of its message and a **timestamp** to know when he posted his message.

`server/Message.js`
```jsx
const mongoose = require('mongoose');

const messageSchema = new mongoose.Schema({
  content: String,
  name: String,
}, {
  timestamps: true,
});

module.exports = mongoose.model('Message', messageSchema);
```

The second one (index.js) is our main file, I won't go too much into details because that would make this tutorial a bit too long, but feel free to ask any question in the comments, I'll be glad to answer them or improve the comments directly in the code if necessary.

`server/index.js`
```jsx
const express = require('express');
const app = express();
const http = require('http').Server(app);
const path = require('path');
const io = require('socket.io')(http);

const uri = process.env.MONGODB_URI;
const port = process.env.PORT || 5000;

const Message = require('./Message');
const mongoose = require('mongoose');

mongoose.connect(uri, {
  useUnifiedTopology: true,
  useNewUrlParser: true,
});

app.use(express.static(path.join(__dirname, '..', 'client', 'build')));

io.on('connection', (socket) => {

  // Get the last 10 messages from the database.
  Message.find().sort({createdAt: -1}).limit(10).exec((err, messages) => {
    if (err) return console.error(err);

    // Send the last messages to the user.
    socket.emit('init', messages);
  });

  // Listen to connected users for a new message.
  socket.on('message', (msg) => {
    // Create a message with the content and the name of the user.
    const message = new Message({
      content: msg.content,
      name: msg.name,
    });

    // Save the message to the database.
    message.save((err) => {
      if (err) return console.error(err);
    });

    // Notify all other users about a new message.
    socket.broadcast.emit('push', msg);
  });
});

http.listen(port, () => {
  console.log('listening on *:' + port);
});
```

The structure of your project should now look like this:

```
speedchatapp/
├── client/
│   └── (Several files and folders)
└── server/
    ├── node_modules/
    ├── .gitignore
    ├── index.js
    ├── Message.js
    ├── package-lock.json (auto-generated)
    └── package.json
```

Before coming back to our React app to finish our project, let's set up our Heroku hosting and link it to our Github repository to make sure the deployment works fine.

# Set up our Heroku hosting

Let's download and install the [Heroku CLI](https://devcenter.heroku.com/articles/heroku-cli) to set up everything from our Terminal.

Once downloaded and installed, let's go back to our Terminal and login to our Heroku account:

```
heroku login
```

It will open a new tab in your browser and once you are logged in, you can close the browser tab and go back to your Terminal.

Now let's create our new app that will host our project:

```
heroku create
```

It will automatically generate an identifier with a URL where you can access your app, it should look like this:

https://sleepy-meadow-81798.herokuapp.com/

![Heroku App](https://thepracticaldev.s3.amazonaws.com/i/5zlpzxw4wdmrq4fdr7n7.png)

You can rename your app if you want something a little bit easier to remember, you can then use it for the rest of this tutorial:

![Rename the app in Heroku UI](https://thepracticaldev.s3.amazonaws.com/i/kcey0m3znyk70oefwy7b.png)

Alright, now we need our MongoDB database to store the chat messages from the users. Let's add the mongolab addon to our app:

```
heroku addons:create mongolab --app speedchatapp
```

I used `speedchatapp` in the previous command because I renamed my application but you should use the one provided when you created it if you didn't rename it, for example, `sleepy-meadow-81798`.

Once created it will show you the name of a variable in green, i.e `MONGODB_URI`. Now let's get the configuration URI of our newly created database:

```
heroku config:get MONGODB_URI
```

You should see something like this:

```
mongodb://heroku_123abc:abc123@ds141188.mlab.com:41188/heroku_123abc
```

Copy this URI, and create a file at the root of your project called `.env` with the following content `[VARIABLE_IN_GREEN]=[URI]`. It should look like this:

```
MONGODB_URI=mongodb://heroku_123abc:abc123@ds141188.mlab.com:41188/heroku_123abc
```

Let's copy one more time the `.gitignore` and add the `.env` file at the end of it to avoid pushing the credentials of our database to GitHub:

```
cp server/.gitignore ./
echo '.env' >> .gitignore
```

During the deployment of our app, we need to tell Heroku how to start our server. It can be done by using a [Procfile](https://devcenter.heroku.com/articles/procfile) that we will put at the root of our project. So let's create it and add the command line that will start our server:

```
echo 'web: node server/index.js' > Procfile
```

Now let's initialize another `package.json` at the root of our project. Same as before, don't worry about all the options, for now, just type <kbd>Enter</kbd> at all prompts:

```
npm init
```

One last thing we want to do here is to install the npm package called [Concurrently](https://github.com/kimmobrunfeldt/concurrently) that will allow us to run both the server and the client in a single command line during our development mode:

```
npm install --save-dev concurrently
```

And finally, in our newly created `package.json` at the root of the project, we will add 2 lines in the `scripts` section:

```
"scripts": {
    "dev": "concurrently --kill-others \"heroku local\" \"npm run start --prefix ./client\"",
    "postinstall": "npm install --prefix ./server && npm install --prefix ./client && npm run build --prefix ./client",
}
```

The `postinstall` command, as you can guess, will be executed after Heroku has finished running the `npm install` command at the root of our folder. It's telling Heroku to also run the `npm install` command inside our `client` and `server` folder and will also [build our React app for production](https://create-react-app.dev/docs/production-build/).

Now, it's time to test it, go to the root of your project and type:

```
npm run dev
```

This will launch the server and our React app in development mode, and it should open a window in your browser with the previous landing page of our React app.

In your terminal, you should see something like this:

```
> concurrently --kill-others "heroku local" "npm run start --prefix ./client"

[1] 
[1] > react-scripts start
[1] 
[0] [OKAY] Loaded ENV .env File as KEY=VALUE Format
[0] 12:16:15 PM web.1 |  listening on *:5000
[1] Starting the development server...
[1] 
[1] Compiled successfully!
[1] 
[1] You can now view client in the browser.
[1] 
[1]   Local:            http://localhost:3000/
[1]   On Your Network:  http://192.168.0.10:3000/
[1] 
[1] Note that the development build is not optimized.
[1] To create a production build, use npm run build.
```

Note: we are using the same database for both Dev and Live mode, if you want to use a different database, you can always create another one in Heroku like we have seen before and update your `.env` file with the credentials of your new database to make sure it won't interfere with the one in production.

# Set up GitHub and link to Heroku

Now, we are going create a new repository on GitHub, and we are going to connect it to Heroku so every time we will merge a Pull Request on the master branch, it will automatically deploy it on Heroku.

Let's create our repository on GitHub. Go to https://github.com/new:

![Create a new repository on Github](https://thepracticaldev.s3.amazonaws.com/i/g5oou6fpijroyjwa1nmn.png)

Write down the repository URL that we will use in the next step. Back to our Terminal, in the root folder of our project:

```
// Initialize the root folder as a Git repository
git init 

// Add all the files for the initial commit
git add .

// Commit staged files
git commit -m "Initial commit"

// Set the GitHub remote repository
git remote add origin <repository url>

// Push the local changes to GitHub
git push origin master
```

Now our code is on GitHub, let's link this repository to our Heroku app.

From the [Heroku UI](https://dashboard.heroku.com/apps), select your app and click on the `Deploy` tab. In the `Deployment method`, click on `Github`, type your repository name and click on `Connect`:

![Connect to GitHub](https://thepracticaldev.s3.amazonaws.com/i/kauna1ndvjani6ox8xok.png)

Also, make sure that the "Enable Automatic Deploys" on the `master` branch is activated:

![Enable Automatic Deploys](https://thepracticaldev.s3.amazonaws.com/i/ijwo1byrjuy9vciu3yxe.png)

It should now look like this:

![Automatic Deploys Enabled](https://thepracticaldev.s3.amazonaws.com/i/cs8t0xk8v90a4d5u5hkd.png)

Now let's trigger a first manual deployment to check that everything is fine. Click on the `Deploy Branch` and wait until you see you see `Your app was successfully deployed`.

Finally, after clicking on the `Open App` button at the top right of the page, you should see the React app on your Heroku hosting.

From now on, after pushing any update to your GitHub repository, you should see the deployment triggered automatically in your Heroku UI:

![Deployment triggered](https://thepracticaldev.s3.amazonaws.com/i/58eimgv646ptitxu3y7b.png)

# Finishing the client

Now that the architecture of our project is ready, let's finish our `client `React app.

The first thing we will need here is to install our frontend dependencies in the `client` folder: Socket.IO for client, Material-UI core and icons:

```
cd client/
npm install socket.io-client @material-ui/core @material-ui/icons --save
```

Now in the `client/package.json`, add the following `proxy` field at the end of the file:

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

It will tell the development server to proxy any unknown requests to your server in development. Check the [official documentation for more information](https://create-react-app.dev/docs/proxying-api-requests-in-development/).

Next, we'll create a `config.js` file to tell our app to switch endpoints in case we are on our local machine or live hosting:

### client/src/config.js

```jsx
import pkg from '../package.json';

export default {
  development: {
    endpoint: pkg.proxy
  },
  production: {
    endpoint: window.location.hostname
  }
}
```

Okay now let's start our local development environment from our root folder:

```
npm run dev
```

# Last steps

For the last step, either create or update each file below manually or go directly to the GitHub repository to check out the project.

Replace `client/src/App.css`:
```css
body {
  background: #f5f5f5;
  padding: 16px;
}

#chat {
  max-height: calc(100vh - 128px);
  overflow: scroll;
  padding: 16px;
}

.name {
  color: rgba(0, 0, 0, 0.54);
}

.content {
  margin-bottom: 8px;
}
```

Replace `client/src/App.js`:
```jsx
import React from 'react';
import config from './config';
import io from 'socket.io-client';

import Paper from '@material-ui/core/Paper';
import Typography from '@material-ui/core/Typography';

import BottomBar from './BottomBar';
import './App.css';

class App extends React.Component {
  constructor(props) {
    super(props);

    this.state = {
      chat: [],
      content: '',
      name: '',
    };
  }

  componentDidMount() {
    this.socket = io(config[process.env.NODE_ENV].endpoint);

    // Load the last 10 messages in the window.
    this.socket.on('init', (msg) => {
      let msgReversed = msg.reverse();
      this.setState((state) => ({
        chat: [...state.chat, ...msgReversed],
      }), this.scrollToBottom);
    });

    // Update the chat if a new message is broadcasted.
    this.socket.on('push', (msg) => {
      this.setState((state) => ({
        chat: [...state.chat, msg],
      }), this.scrollToBottom);
    });
  }

  // Save the message the user is typing in the input field.
  handleContent(event) {
    this.setState({
      content: event.target.value,
    });
  }

  //
  handleName(event) {
    this.setState({
      name: event.target.value,
    });
  }

  handleSubmit(event) {
    // Prevent the form to reload the current page.
    event.preventDefault();

    // Send the new message to the server.
    this.socket.emit('message', {
      name: this.state.name,
      content: this.state.content,
    });

    this.setState((state) => {
      // Update the chat with the user's message and remove the current message.
      return {
        chat: [...state.chat, {
          name: state.name,
          content: state.content,
        }],
        content: '',
      };
    }, this.scrollToBottom);
  }

  // Always make sure the window is scrolled down to the last message.
  scrollToBottom() {
    const chat = document.getElementById('chat');
    chat.scrollTop = chat.scrollHeight;
  }

  render() {
    return (
      <div className="App">
        <Paper id="chat" elevation={3}>
          {this.state.chat.map((el, index) => {
            return (
              <div key={index}>
                <Typography variant="caption" className="name">
                  {el.name}
                </Typography>
                <Typography variant="body1" className="content">
                  {el.content}
                </Typography>
              </div>
            );
          })}
        </Paper>
        <BottomBar
          content={this.state.content}
          handleContent={this.handleContent.bind(this)}
          handleName={this.handleName.bind(this)}
          handleSubmit={this.handleSubmit.bind(this)}
          name={this.state.name}
        />
      </div>
    );
  }
};

export default App;
```

Create `client/src/BottomBar.js`:
```jsx
import React from 'react';

import { fade, makeStyles } from '@material-ui/core/styles';
import AppBar from '@material-ui/core/AppBar';
import InputBase from '@material-ui/core/InputBase';
import Toolbar from '@material-ui/core/Toolbar';

import ChatIcon from '@material-ui/icons/Chat';
import FaceIcon from '@material-ui/icons/Face';

const useStyles = makeStyles(theme => ({
  appBar: {
    bottom: 0,
    top: 'auto',
  },
  inputContainer: {
    backgroundColor: fade(theme.palette.common.white, 0.15),
    '&:hover': {
      backgroundColor: fade(theme.palette.common.white, 0.25),
    },
    borderRadius: theme.shape.borderRadius,
    marginLeft: theme.spacing(1),
    position: 'relative',
    width: '100%',
  },
  icon: {
    width: theme.spacing(7),
    height: '100%',
    position: 'absolute',
    pointerEvents: 'none',
    display: 'flex',
    alignItems: 'center',
    justifyContent: 'center',
  },
  inputRoot: {
    color: 'inherit',
  },
  inputInput: {
    padding: theme.spacing(1, 1, 1, 7),
    width: '100%',
  },
}));

export default function BottomBar(props) {
  const classes = useStyles();

  return (
    <AppBar position="fixed" className={classes.appBar}>
      <Toolbar>
        <div className={classes.inputContainer} style={{maxWidth: '200px'}}>
          <div className={classes.icon}>
            <FaceIcon />
          </div>
          <InputBase
            onChange={props.handleName}
            value={props.name}
            placeholder="Name"
            classes={{
              root: classes.inputRoot,
              input: classes.inputInput,
            }}
            inputProps={{ 'aria-label': 'name' }}
          />
        </div>
        <div className={classes.inputContainer}>
          <form onSubmit={props.handleSubmit}>
            <div className={classes.icon}>
              <ChatIcon />
            </div>
            <InputBase
              onChange={props.handleContent}
              value={props.content}
              placeholder="Type your message..."
              classes={{
                root: classes.inputRoot,
                input: classes.inputInput,
              }}
              inputProps={{ 'aria-label': 'content' }}
            />
          </form>
        </div>
      </Toolbar>
    </AppBar>
  );
}
```

Every time you update the code, you should see the project at http://localhost:3000 automatically reload with the last changes.

Finally, let's push our latest update to GitHub to trigger a new deployment on our live project:

```
git add .
git commit -m "Final update"
git push origin master
```

Et voilà, Bob's your uncle! Our chat is now finished and ready: https://speedchatapp.herokuapp.com/

If you have any question, feel free to ask in the comments, I'll be glad to answer it and improve this tutorial. And feel free to [fork the project to improve it](https://github.com/armelpingault/speedchatapp) ;)