[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
The app name you choose will later become have a default domain. i.e. https://mern-demo-prod.herokuapp.com
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
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
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 staticallyapp.use(express.static(path.join("front-end", "build")))
// Set CORS headerapp.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 Routeapp.use(userRoutes)
app.use((req, res, next) => { res.sendFile(path.resolve(__dirname, "front-end", "build", "index.html")) })
// Error Handlerapp.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 connectionmongoose .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
"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
. Changeto"build": "react-scripts build"
"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
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
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
.
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
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
git init && heroku git:remote -a <YOUR_APP_NAME>
Make sure to have Heroku CLI installed
Finally, to push your project,
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
"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.
- [React + Node.js]: Create your MERN Stack Application - Part 2: Frontend
- [React + Node.js]: Deploy Your MERN Stack App to Amazon EC2 with SSL Encryption