Deploying a MERN Application using MongoDB Atlas to Heroku

March 21st, 2020

#mern#heroku

Blog Cover

Table of contents

Introduction to MERN

In this article, I'd be building and deploying an application built with the MERN stack to Heroku.

MERN which stands for MongoDB, Express, React and NodeJS is a popular tech stack used in building web applications which involves frontend work (with React), backend work (with Express and NodeJS) and a database (with MongoDB).

Heroku on the other hand is a platform as a service (PaaS) that enables developers to build, run, and operate applications entirely in the cloud - Documentation

For this article, we'd be using MongoDB Atlas which is a global cloud database service for modern applications. This is more secure than the MongoDB installed locally on our server and it also gives room for more resources on our servers.

We'd be building a simple react project, which makes post requests to an api to add a user and can also make get requests to get all users.

You can skip to any step with the table of contents listed above.

Building Process

You may already have a MERN project which is to be deployed to Heroku, but I'd go over a little project just incase you don't

Building React App

Note: Before we begin with our project, node must be installed on your computer. node also provides us with npm which is used for installing packages.

Install create-react-app

create-react-app is used to create a starter react app.

If you do not have create-react-app installed before, type the following in the command line.

npm i create-react-app -g

-g flag installs the package globally.

Create project directory

create-react-app my-project
cd my-project

The above creates a directory 'my-project' and installs dependencies which would be used in the react starter. After which the directory is changed to the project directory.

Start project and make necessary edits

npm start

The above starts the react project, which gives you a url which you preview the project. You make necessary edits on the project like changing images or texts.

Install axios

npm i axios --save

axios is a javascript library used to make HTTP requests easily. It would be used to send requests from the frontend (React) to the apis provided by the backend

Creating the backend

The backend manages the apis, handles requests and also connects to the database.

Install backend packages

npm i express cors mongoose body-parser --save
  1. express: Express is a minimal and flexible Node.js web application framework that provides a robust set of features for web applications - Documentation.
  2. cors: CORS is a node.js package for providing a Connect/Express middleware that can be used to enable CORS with various options - Documentation.
  3. mongoose: Mongoose is a MongoDB object modeling tool designed to work in an asynchronous environment. Mongoose supports both promises and callbacks - Documentation.
  4. body-parser: Node.js body parsing middleware. - Documentation.

Create backend folder

mkdir backend
cd backend

Configure backend

Create entry point server.js

First, create a server.js file, which would be the entry point to the backend

touch server.js

In server.js, type the following

const express = require('express');
const bodyParser = require('body-parser');
const cors = require('cors');
const path = require('path')
const app = express();
require('./database');
-----
app.use(bodyParser.json());
app.use(cors());
-----
// API
const users = require('/api/users');
app.use('/api/users', users);
-----
app.use(express.static(path.join(__dirname, '../build')))
app.get('*', (req, res) => {
    res.sendFile(path.join(__dirname, '../build'))
})
-----
const port = process.env.PORT || 5000;
app.listen(port, () => {
    console.log(`Server started on port ${port}`);
});

express.static delivers static files which is the one built when npm run build is ran on a react project. Remember, the built file is in the build folder.

From our configuration, any request sent to /api/users will be sent to users api which we about to configure

Configure users api
mkdir api
touch api/users.js

In touch.js, add the following

const express = require('express');
const router = express.Router()
-----
const User = require('../models/User');
-----
router.get('/', (req, res) => {
    User.find()
        .then(users => res.json(users))
        .catch(err => console.log(err))
})
-----
router.post('/', (req, res) => {
    const { name, email } = req.body;
    const newUser = new User({
        name: name, email: email
    })
    newUser.save()
        .then(() => res.json({
            message: "Created account successfully"
        }))
        .catch(err => res.status(400).json({
            "error": err,
            "message": "Error creating account"
        }))
})
module.exports = router

In the above, we create a get and post request handler which fetches all users and posts users. Fetching and adding user to the database is aided by the User model created. Let's create the model

Create User model
mkdir models
touch models/user.js

In user.js, add the following

const mongoose = require('mongoose');
const Schema = mongoose.Schema;
-----
const userSchema = new Schema({
    name: {
        type: String,
        required: true
    },
    email: {
        type: String,
        required: true
    }
})
module.exports = mongoose.model("User", userSchema, "users")

In the above, a schema is created for the user which contains the fields of the user. At the end of the file, the model ("User") is exported with the schema and the collection ("users")

Connect MongoDB Atlas database

According to the docs

MongoDB Atlas is the global cloud database service for modern applications.

Firstly, we'd need to register on Mongo cloud.

Go through this documentation to create an Atlas account and create your cluster.

One thing worth noting is whitelisting your connection IP address. If this step is ignored, you won't have access to the cluster so pay attention to that step.

The cluser is a small server which would manage our collections (similar to tables in SQL databases). To connect your backend to the cluster, create a file database.js which as we can see is required in server.js, then enter the following:

const mongoose = require("mongoose")
const connection =
  "mongodb+srv://username:<password>@<cluster>/<database>?retryWrites=true&w=majority"
mongoose
  .connect(connection, {
    useNewUrlParser: true,
    useUnifiedTopology: true,
    useFindAndModify: false,
  })
  .then(() => console.log("Database Connected Successfully"))
  .catch((err) => console.log(err))

In the connection variable, enter your username (for MongoDB cloud), your password (cluster password), your cluster (address for your cluster) and the database (name of your database). All these can be easily discovered if you followed the documentation

Calling APIs on the frontend

All APIs would be made to localhost:5000 locally just as we set up in server.js. When deployed to heroku, the server would use the port provided by the server (process.env.PORT).

To make things easier, React allows us to specify a proxy which requests would be sent to.

Open package.json and just before the last curly brace, and add the following:

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

This way, we can directly send requests to api/users, and when our site is deployed and built, the default port of our application would be used with the same api.

Open App.js for React and add the following

const App = function () {
  const [users, setUsers] = useState(null)
  const [username, setUsername] = useState("")
  const [email, setEmail] = useState("")
  useEffect(() => {
    axios
      .get("/api/users")
      .then((users) => setUsers(users))
      .catch((err) => console.log(err))
  }, [])
  function submitForm() {
    if (username === "") {
      alert("Please fill the username field")
      return
    }
    if (email === "") {
      alert("Please fill the email field")
      return
    }
    axios
      .post("/api/users", {
        username: username,
        email: email,
      })
      .then(function () {
        alert("Account created successfully")
        window.location.reload()
      })
      .catch(function () {
        alert("Could not creat account. Please try again")
      })
  }
  return (
    <>
      <h1>My Project</h1>
      {users === null ? (
        <p>Loading...</p>
      ) : users.length === 0 ? (
        <p>No user available</p>
      ) : (
        <>
          <h2>Available Users</h2>
          <ol>
            {users.map((user, index) => (
              <li key={index}>
                Name: {user.name} - Email: {user.email}
              </li>
            ))}
          </ol>
        </>
      )}

      <form onSubmit={submitForm}>
        <input
          onChange={(e) => setUsername(e.target.value)}
          type="text"
          placeholder="Enter your username"
        />
        <input
          onChange={(e) => setEmail(e.target.value)}
          type="text"
          placeholder="Enter your email address"
        />
        <input type="submit" />
      </form>
    </>
  )
}
export default App

useState and useEffect hooks are used to handle state and sideEffects. What is basically happening is that the first state of users is null, and at null, 'Loading...' is showed on the browser. At useEffect, [] is used to specify that at componentDidMount (when the component is mounted), make a axios request to the api which is running on localhost:5000. If the result is gotten and there is no user, 'No user available' is displayed to the user else a numbered list of the users is displayed.

If you want to learn more about about useState and useEffect, check out this article - What the heck is React Hooks?

With the form available, a post request can be made to post a new user. The state of the inputs are controlled and sent to the api on localhost:5000 on submission. After wards, the page is refreshed and the new user is displayed.

Deploying to Heroku

The most exciting part (I guess 😁)

To deploy your application to heroku, you must have a heroku account. Go to their page to create an account. Then go through their documention on how to create an heroku app. Also check out the documentation on Heroku CLI

Create Heroku App

First, login to heroku:

heroku login

This will redirect you to a url on the browser of which you can log in, then you can continue in the terminal.

In the same react project directory, run the following:

heroku create

Tnis would create a heroku application and also give you the url to access the application.

Configure package.json

Heroku uses your package.json file to know which scripts to run and dependencies to install for your project to run successfully.

In your package.json, add the following:

{
    ...
    "scripts": {
        ...
        "start": "node backend/server.js",
        "heroku-postbuild": "NPM_CONFIG_PRODUCTION=false npm install npm && run build"
    },
    ...
    "engines": {
        "node": "10.16.0"
    }
}

From the above, heroku runs a post build which as you can see installs your dependencies and runs a build of your react project. Then it starts your project with the start script which basically starts your server. And your project works fine.

engines specifies the versions of engines like node and npm to install.

Push to Heroku

git push heroku master

This pushes your codes to heroku. Remember to include necessary files in .gitignore.

After few seconds, you site is ready. If there are any errors, you can check your terminal for results after pushing or going to your dashboard on the browser to view build logs.

Now you can preview your site on the url heroku sent when heroku create was ran.


That's all there is to this article. Glad you read it this far.

Wrap Up

Of course there is more to MERN stack applications.

This article did not go as deep as authentications, login, sessions and all that, but just to make deploying MERN stack applications to heroku and working with MongoDB Atlas easy.

Thanks for reading.


Connect with me ✨