[React + Node.js]: Deploy Your MERN Stack App on Heroku

deploy

03/13/2020


This post is to show how to deploy your MERN Stack (MongoDB, Express.js, React, Node.js) applications on Heroku. Heroku is one of the cloud services where you can deploy, manage, and scale modern applications.

Prerequisite

If you don't have MERN Stack project available, here is how you can set up a basic MERN stack application with REST API.

- [React + Node.js]: Create your MERN Stack Application - Part 1: Backend

- [React + Node.js]: Create your MERN Stack Application - Part 2: Frontend

You can also choose to download the project. I'll be using this project to deploy on Heroku.

Creating your App on Heroku

Go on Heroku website to sign up/sign in. When you're signed in, create a new app with an app name 1

The app name you choose will later become have a default domain. i.e. https://mern-demo-prod.herokuapp.com

2

After creating app, install Heroku CLI from this link. After installation of CLI, move onto the next time. We'll come back to Heroku later.

Serve index.html

Current file structure

C
mern-demo
├── controllers
| ├── user.js
├── front-end
| ├── src
| | ├── App.css
| | ├── App.js
| | ├── custom-button.jsx
| | ├── user-add-form.jsx
| | ├── user.jsx
| | └── ...
| ├── .env.production
| ├── .env
| └── ...
├── models
| ├── user.js
├── routes
| ├── user.js
├── app.js
├── config.js
└── ...

Before deploying your app to Heroku, your server needs to be able to serve the minified version of index.html that will later be created with npm run build inside mern-demo/front-end.

You need to serve index.html in mern-demo/front-end/build directory. Modify mern-demo/app.js accordingly.

mern-demo/app.js

JS
const express = require("express")
const mongoose = require("mongoose")
const { MONGODB_URI } = require("./config")
const userRoutes = require("./routes/user")
const path = require("path")
const app = express()
app.use(express.json())
// Serves mern-demo/front-end/build statically
app.use(express.static(path.join("front-end", "build")))
// Set CORS header
app.use((req, res, next) => {
res.setHeader("Access-control-Allow-Origin", "*")
res.setHeader("Access-Control-Allow-Methods", "GET, POST, DELETE")
res.setHeader("Access-Control-Allow-Headers", "Content-Type")
next()
})
// Register Route
app.use(userRoutes)
app.use((req, res, next) => {
res.sendFile(path.resolve(__dirname, "front-end", "build", "index.html"))
})
// Error Handler
app.use((error, req, res, next) => {
const status = error.statusCode || 500
const message = error.message
const data = error.data // Passing original error data
res.status(status).json({ message: message, data: data })
})
// DB connection
mongoose
.connect(MONGODB_URI, {
useNewUrlParser: true,
useUnifiedTopology: true,
useFindAndModify: false,
})
.then(result => {
const port = process.env.PORT || 8080
app.listen(port, () => {
console.log(`Listening on port ${port}...`)
})
})
.catch(err => {
// Handle error
})

Create Production Build

Now that mern-demo/front-end/build is being served.. but it is empty. Define another script to build front-end in mern-demo/package.json

JSON
"scripts": {
"install-client": "npm i --prefix front-end",
"start": "node app.js",
"server": "nodemon app.js",
"client": "npm start --prefix front-end",
"dev": "concurrently \"npm run server\" \"npm run client\"",
"build-client": "npm run build --prefix front-end"
}

(Optional): if you want to prevent users from viewing your React source code via inspect, you can add an option to production build inside mern-demo/front-end/package.json. Change "build": "react-scripts build" to "build": "GENERATE_SOURCEMAP=false react-scripts build",

Define your fetch url in .env.production

Before running the build script, make sure to have fetch url updated to your app name on Heroku for production in mern-demo/front-end/.env.production. For example:

.env.production

TEXT
REACT_APP_FETCH_URL=https://mern-demo-prod.herokuapp.com

Make sure NOT to include / at the end because our code fetches with..

fetch(process.env.REACT_APP_FETCH_URL + "/user") which already contains /

When you're done, run the following command inside mern-demo directory

BASH
npm run build-client

Confirm that mern-demo/front-end/build contains the production build file

Whitelist IP on MongoDB

Whitelist IP on MongoDB defines which IP addresses can have access to your cluster. In other words, you can specify IP addresses that can fetch/add/delete users--you can think of it as an extra layer of security. Here I'll allow access from anywhere.

5 6

Back on Heroku

We're ready to push our project to Heroku. If you have your project stored in git repository, add heroku remote & push

BASH
heroku git:remote -a <YOUR_APP_NAME>
git push heroku master

Make sure to update your .gitignore to exclude any sensitive information

Otherwise, if you don't have your git repository set up, run the following inside mern-demo folder to create repository for Heroku

BASH
git init && heroku git:remote -a <YOUR_APP_NAME>

Make sure to have Heroku CLI installed

Finally, to push your project,

BASH
git add .
git commit -am "[INITIAL]: initial commit"
git push heroku master

Make sure to update your .gitignore to exclude any sensitive information

That's it! Your app will soon be able to available on https://<YOUR_APP_NAME>.herokuapp.com, as soon as build is complete on their end. You can view the building status on the Overview tab.

Post Build Script

Don't like running build script for your client code every time before deploying your app? There is an easier way by adding post build script in mern-demo/package.json

JSON
"scripts": {
"install-client": "npm i --prefix front-end",
"start": "node app.js",
"server": "nodemon app.js",
"client": "npm start --prefix front-end",
"dev": "concurrently \"npm run server\" \"npm run client\"",
"build-client": "npm run build --prefix front-end",
"heroku-postbuild": "NPM_CONFIG_PRODUCTION=false npm i --prefix front-end && npm run build --prefix front-end"
},

The added post-build script gets run on their server every time after you push your project to Heroku by git push heroku master. Therefore, you don't have to run npm run build on your own.

Caveat: If you have .env.production included in .git-ignore file, you would have to exclude it because Heroku needs to have access to the file to grab environment variables (for server side). If it's sensitive information, you shouldn't have it on the client side but to have it as config variable described below

Including environment variables

If you happen to have environment variable set up for your server side, you can add those variables by going to Settings tab and clicking Reveal Config Vars. Alternatively, you can view those variables by typing heroku config in command line interface.

More Notes

  • You can add your custom domain with Heroku at no cost; however, in order to configure SSL encryption with Heroku, you would need to purchase paid dyno. The default subdomain <YOUR_APP_NAME>.herokuapp.com already contains SSL encryption.

WRITTEN BY

Keeping a record