Introduction

This tutorial assumes you have a MERN application built. If you don't, you can build one with my two part tutorial: Build an API with Node.js, Express and MongoDB and Integrate React into a Node application.


Dotenv Package and environmental variables

Heroku requires you to use a cloud database platform for MongoDB. MongoDB's Atlas cloud service requires a user name and password as part of the URL. To keep these private you should use environmental variables so that they are not explicitly in your application's code. Assuming you are using Atlas for both development and production, you should add the dotenv package for your development environment if you expect to expose the file in a place like a public github repository. From the project's root directory:

  • npm install dotenv
  • In Node, environment variables can be accessed on the process.env object. The dotenv middleware looks for a file called .env and loads it's contents into the process.env variable for use in your code.

    Create a .env file in the project's root directory:

  • touch .env
  • Add environmental variables on new lines in the form of NAME=VALUE. By convention, use names in all upper case with words separated by underscores like MONGODB_URI. Add your MongoDB Atlas database link since it contains your user name and password. Something like this:

    # .env
    MONGODB_URI=mongodb+srv://user:password@cluster-number.mongodb.net/test?retryWrites=true&w=majority
    

    We are only using the .env file in our development environment. When you deploy your app on Heroku you need to add this as an environmental variable on Heroku at that time.


    Server.js Update

    Update the server.js file with changes related to Heroku highlighted and explained below.

    // server.js
    
    const express = require('express');
    const mongoose = require('mongoose');
    const router = require('./routes/index');
    const path = require('path');           #1
    const PORT = process.env.PORT || 3001;  #2
    require('dotenv').config();             #3
    
    const app = express();
    
    app.use(express.urlencoded({ extended: true }));
    app.use(express.json());
    app.use('/api', router);
    
    mongoose.connect(process.env.MONGODB_URI, { useNewUrlParser: true }); #4
    mongoose.connection.once('open', () => {
      console.log('Connected to the Database.');
    });
    mongoose.connection.on('error', err => {
      console.log('Mongoose Connection Error : ' + err);
    });
    
    if (process.env.NODE_ENV === 'production') {           #5
      app.use(express.static('client/build'));
    
      app.get('*', (req, res) => {
        res.sendFile(path.resolve(__dirname, 'client', 'build', 'index.html'));
      });
    }
    
    app.listen(PORT, () => {
      console.log(`Server listening on port ${PORT}.`);
    });
    
    1. Path is a module within Node so we don't install it with npm. The path module provides utilities for working with file and directory paths and is needed to work on Heroku.
    2. Heroku will assign a port via the PORT environmental variable. We'll use the or operator || to assign that as the port or 3001 if it is null (i.e., if we are in the dev environment).
    3. Require and configure dotenv middleware.
    4. Connect to the database URI taken from the environmental variable.
      You could use a local MongoDB instance for your development environment and use the cloud for production. If you take that route then you would would set it up like this:
      const LOCAL_DB = "mongodb://127.0.0.1:27017/my_local_db";
      mongoose.connect(process.env.MONGODB_URI || LOCAL_DB, { useNewUrlParser: true });
    5. When you deploy your app to Heroku, Heroku will run the build command on the React app and move the finished version to the build folder. This conditional statement tells Heroku that your React files will be in the client/build folder and URL requests will go to the client/build/index.html file where your SPA React app is served from.

    Package.json

    The React app in development is not optimized. We need to run the npm build (or yarn build) command for React to minify all your React code and put it into a folder called build. But actually we'll instruct Heroku to do that for you when you deploy. Add a script to the package.json file in the project root directory with property heroku-postbuild (bolded below). Heroku will run that script after it builds your app. The script cds into the client directory, runs npm install to install the React packages, and npm run build to build the optimized version in the build folder. Your finished package.json file should look something like the below.

    // package.json
    {
      "name": "mern-app-tutorial",
      "version": "1.0.0",
      "description": "Web app built with Node.js, Express, MongoDB, and React",
      "main": "server.js",
      "dependencies": {
        "dotenv": "^8.0.0",
        "express": "^4.17.1",
        "mongoose": "^5.6.3"
      },
      "devDependencies": {
        "concurrently": "^4.1.1"
      },
      "scripts": {
        "start": "node server.js",
        "dev": "concurrently \"nodemon server.js\" \"cd client && npm run start\"",
        "heroku-postbuild": "cd client && npm install && npm run build"
      },
      "keywords": [],
      "author": "",
      "license": "MIT"
    }
    

    Also note the "start" script. We used that in development to start the Express server with npm start. But we ignored it with our npm run dev script, using nodemon instead. But for production this script will come in handy since we want Heroku to run our server file with node once it's built.

    Once Heroku is done building the app it will look for a file called Procfile for instructions on how to start the app. So we could create a Procfile and add one line web: npm run start. If Heroku doesn't find a Procfile it will run the command npm start. So that's why we need the start script to run node server.js.


    Git repository

    We will use git to push our app to Heroku.

    When we generated our React app with create-react-app, it generated a git repository and created a gitignore file in the client folder. We want one git repository in our root directory for both our API and our React client. So if you haven't already, delete the .git repository in the client folder. The gitignore file is fine where it is. Git will honor multiple gitignore files so we'll just add another one at the root directory.

    Now in our project's root directory initate a new git repository and create a .gitignore file.

  • git init
  • touch .gitignore
  • Add the .env file and the node_modules folders to our new gitignore file.

    // .gitignore
    
    node_modules
    .env
    

    Set up Heroku

    This tutorial assumes you are already familiar with Heroku. If not, you need to first create an account on their website: heroku.com
    Then download and install the Heroku Command Line Interface on your computer devcenter.heroku.com/articles/heroku-cli.

    From the CLI, create a new Heroku App:

  • heroku create appname
  • If your appname is already taken you'll have to pick another one. Or leave off the appname and let Heroku generate a super awesome one for you. If you have a real domain name you can configure Heroku to use that devcenter.heroku.com/articles/custom-domains

    This will automatically set heroku as your git remote repository. Run the below command to confirm. It will list the project's remote git repositories (if any) and their urls.

  • git remote -v
  • Set the MongoDB uri as an environmental variable (no quotes):

  • heroku config:set MONGODB_URI=Your db cloud link goes here
  • As of this writing there is no Heroku Add-On for the the MongoDB Atlas cloud service.


    Deploy your app

    Create your initial git commit:

  • git add -A
  • git commit -m "Initial commit"
  • Then push the code out to heroku:

  • git push heroku master
  • It will take a few minutes to load up. When it's finished launch the app. The below command will open the app in your browser:

  • heroku open
  • If everything went well you have a working MERN application up and running on Heroku!