Deploying your MERN project with Heroku (using your Master Git Branch)
Heroku is an excellent service that specializes in making deployment of your web application quick and painless. Fortunately for us, it also has a free tier that will be perfect for our needs. There will only be a little bit of setup needed to get your web app up and running online. Let's get started.
Note: Heroku only allows you to deploy backend or full-stack projects. You can't use Heroku for deploying frontend-only projects without creating a small web server to serve it up. If you want to deploy a frontend-only site, we recommend looking at the insanely simple tool Surge. Check out our writeup on deploying a frontend site with Surge
Creating your Heroku app
1. Create a free Heroku account
Visit heroku.com and sign up for an account. Choose "Node.js" under the "Primary development language" section and click "Create Free Account."
2. Download and install Heroku CLI
Heroku CLI is a command-line tool that can be used to generate and update/manage heroku applications in a variety of ways. Later we'll use this tool to create a Mongolab online database.
Visit the Heroku CLI download page and follow the instructions to install it and get it set up. (If you have Homebrew installed, installing the Heroku CLI with Homebrew is easiest/quickest.) It'll ask you to log in to the Heroku account you just created, and then create a new git repository for your app and link that repo up with your newly-created Heroku app
$ heroku login
Enter your Heroku credentials.
Email: adam@example.com
Password (typing will be hidden):
Logged in as adam@example.com
3. Ensure you have (or create) a git repo for this project
For the deployment to work, you'll need to make sure the project you're deploying is in its own git repo. (It shouldn't be inside your V School assignments
folder. If it is, open finder and drag it outside to be a sibling of the assignments
folder.)
Note: Here is a Sample Repo if you'd like to follow along
Check if this project has its own git repo:
$ cd path/to/your/project
$ ls -al
If the root folder of your project has a .git
folder show up after the ls -al
command, that means this project has its own git repo and you should be set to move forward.
If it doesn't, we'll need to create one. Run git init
in the project root folder to initialize a new git repository for your project.
Note: Heroku provides a few ways to deploy your application. The main two options are to:
- Have heroku create a remote respository to serve your application.
- You can point your heroku app to your personal git repository to have it deploy directly from your own git repo.
We will be doing the latter as it is good practice to treat your
master
branch as the live version of your App.
4. Create a new application
Creating a Heroku application is like creating a space online for your content to live. Since we will be having the Heroku App use our git repository for all of the code, we simple need to create the application on heroku by visiting their website. Later we will tell our heroku app which repository and branch to look at for the code.
Heroku allows you to do most of the configuration for any of your applications either:
-
Through the dashboard on their website, or
-
Through the Heroku CLI you just installed. It's helpful to know that whenever you make a change from one place, it's changed everywhere. The CLI is just a nice way to make these changes without having to leave the Terminal.
Create
Now create your new App on Heroku. To do this:
1. Go to Heroku.com
2. Click on your "Dashboard"
3. Click "New"
4. Click "Create New App"
- Give the app a name and click "Create App". This name is the default url to this app when you're done (ex: myappname.herokuapp.com)
Modify the project to prepare it for deployment
This guide takes a lot of excellent suggestions from Dave Ceddia's article "Create React App with Express in Production". As you can see from the intro of that article, it mentions 3 possible ways (among many others) of arranging the files in your app:
- Keep them together – Express and React files sit on the same machine (with all the React files inside a specific
client
folder), and Express does double duty: serving the React files and also serving API requests.- e.g., a DigitalOcean VPS running Express on port 80
- Split them apart – Host the Express API on one machine, and the React app on another.
- e.g., React app served by Amazon S3, API server running on a DigitalOcean VPS
- Put the API behind a proxy – Express and React app files sit on the same machine, but served by different servers
- e.g., NGINX webserver proxies API requests to the API server, and
also serves React static files
- e.g., NGINX webserver proxies API requests to the API server, and
All of these are legitimate ways to deploy your app. We're choosing method 1 for this tutorial.
Move files around as necessary
Your project should have a structure where the root project folder houses all your server-side related stuff (models
folder, routes
folder, package.json
for the server-side dependencies, .gitignore
, etc.) One of the folders inside your root project should be called client
. This client
folder should be your entire React application.
NOTE: You may have already integrated some of the following information into your current project, but all of these are necessary for your app to work once deployed so make changes as needed.
More visually, the structure should look something like this:
|__client/ ** THIS IS EVERYTHING FROM THE REACT SIDE **
|__ node_modules/
|__ tons of stuff...
|__ public/
|__ index.html
|__ favicon.ico
|__ etc.
|__ src/
|__ index.js
|__ main/
|__ App.js
|__ etc.
|__ models/
|__ user.js
|__ todo.js
|__ etc.
|__ node_modules/
|__ stuff...
|__ routes
|__ userRoutes.js
|__ todoRoutes.js
|__ etc.
|__ .gitignore
|__ package.json
|__ server.js
|__ etc.
Obviously if this is how you set up your project from the beginning, you shouldn't need to rearrange anything. If it isn't, move files around so it is.
For example, maybe you used to have a client
folder with everything React related in it and a server
folder with all your server code in it, both of which were inside the same parent project folder. If so, you would move all the files from your server
folder up one directory (so they were the first files/folders inside the main project folder) and delete your now-empty server
folder. Assuming you now had a client folder with everything related to React in it, you would be set to go.
If you make changes to the folder structure, make sure to run your app and test it out to see if everything is still working. As needed, fix any broken paths you might have created by moving files around.
Get your Express app to serve up your React app
In the development environment, you've likely been running your React app on port 3000 (by running npm start
or yarn start
) and your server on whatever port you chose by running node server.js
. However, we're going to set up Express to do double duty - it will handle API calls like before, but it will also serve up your React app when someone first visits your main site. This just takes a few additions to your main server.js
file. Add the following:
// ... other imports
const path = require("path")
// ... other app.use middleware
app.use(express.static(path.join(__dirname, "client", "build")))
// ...
// Right before your app.listen(), add this:
app.get("*", (req, res) => {
res.sendFile(path.join(__dirname, "client", "build", "index.html"));
});
app.listen(...)
express.static
is in charge of sending static files requests to the client. So when the browser requests logo.png
from your site, it knows to look in the build folder for that.
app.get("*")
is a "catchall" route handler. It needs to be near the bottom of your server file so that it will only be enacted if the API routes above it don't handle the request. It's in charge of sending the main index.html file back to the client if it didn't receive a request it recognized otherwise.
Add a proxy to the client's package.json
When you make the above change, however, your development approach of running the React app on port 3000 and your server on port 8000 will be broken. We can fix it very easily by adding a line to the React app's package.json
(the one in the client
folder):
"proxy": "http://localhost:8000"
So your whole package.json
should look something like this:
{
"name": "client",
"version": "0.1.0",
"private": true,
"dependencies": {
"axios": "^0.17.1",
"react": "^16.2.0",
"react-dom": "^16.2.0",
"react-scripts": "1.0.17"
},
"scripts": {
"start": "react-scripts start",
"build": "react-scripts build",
"test": "react-scripts test --env=jsdom",
"eject": "react-scripts eject"
},
"proxy": "http://localhost:8000"
}
Now when you make a request to "/api/todos"
(or whatever other endpoint), React will notice that the request wouldn't work if it made it to "http://localhost:3000/api/todos"
, so it'll instead use "http://localhost:8000"
as the proxy URL and make API requests to that endpoint instead. Note that this only happens in a development environment from your local machine. When deployed, we'll access both the site and the API from the same host URL.
Set up environment variables
Environment variables are things you can set in the whole environment (your computer, the server's computer, etc.) rather than specifically in your code. In other words, they're variables that are accessible to your whole machine, not just any one file.
Environment variables are useful for storing sensitive information, since they only live on your machine instead of having the values stored in the code. For example, if you created a JWT secret and hard-coded it into your JavaScript file, anyone looking at your code on Github would know the secret and could grab information they shouldn't be able to. Same with database username/password combinations, etc.
In Node, environment variables can be accessed on the process.env
object. (You may have seen something like process.env.PORT || 8000
somewhere.). They conventionally are created in SCREAMING_SNAKE_CASE.
With Heroku, you need to set the environment variables on your newly created Heroku app so it knows which values to use when the project is deployed. You can do this two ways, either online on Heroku's website, or through the command line using the heroku CLI. Since we are not creating a new Heroku remote repository, all environment variables will need to be added using Heroku.com.
Setting environment variables on the website:
- Open your app in Heroku
- Go to "Settings"
- Click "Reveal Config Vars"
- Add a new variable and click "Add". Done!
Implement environment variables in your code
Anywhere in your code you've set something sensitive or something you need to change from one environment to the next (such as a base URL which changes depending on whether the code is running on a server or locally). Some examples of things that should be set in environment variables include:
- Port numbers
- JWT Secrets
- Database connection strings that include username and password
- Base URLs (if you're serving your client and server completely separately)
In fact, if you currently have your port hardcoded to a number right now, you should go change it to an environment variable. Say you had it originally like this:
app.listen(5000)
You should create a variable near the top of your file like this:
const port = process.env.PORT || 5000;
and then change your
app.listen
to use theport
variable instead. This is important for Heroku to work when you deploy. You don't need to set this PORT variable up with Heroku yourself - it will do it for you.
Up to now, you've probably been using "mongodb://localhost:27017/db-name"
for your database connection string. But when deploying to Heroku, you'll need to use an online database service like MongoLab to store your information, since Heroku doesn't have the capability to house/maintain a database for you. That means when developing locally you want to use "mongodb://localhost:27017/db-name"
, but when it's deployed you want it to use the MongoLab connection string instead.
We'll address the MongoDB/MongoLab situation next, but for now, go through your code and add an environment variable anywhere it makes sense. For this class, if your app has authentication with JWTs, you'll want to change your secret
to be something like:
const secret = process.env.SECRET || "some secret passphrase here for local development"
This says to set a local variable called secret
, whose value will either be whatever the environment variable for SECRET
is set to, or (if that's undefined
), a regular string for local development. Make sure not to use the same secret phrase in both places!
You'll also want the dotenv
package. Make sure to require it in your server.js
and use it.
npm install --save dotenv
require("dotenv").config()
Set up MongoLab
Heroku has an easy-to-use addon you can include in your project very easily that spins up a database, gives you a user and password, and creates an environment variable all in one command for you. While you could create your own account and set up your own database, we'll just use this addon to do everything for us.
First, although it is completely free, Heroku requires you enter payment information in your account in case you scale your app up and use anything other than the sandbox free version of MongoLab.
Head to heroku.com and open your Account Settings >> Billing and add a credit/debit card. Again, it won't charge you unless you manually go out of your way to scale up your app beyond the free tier.
Once that's completed, from your project root, type:
$ heroku addons:create mongolab -a <name-of-heroku-app>
You should get a message like the one above that it created the database for you, and it gives you the name of the environment variable. Copy that name (MONGODB_URI
in the image above) and add it in your server where you connect to the database:
mongoose.connect(process.env.MONGODB_URI || "mongodb://localhost:27017/todos")
// Or wherever you specify your database string
Again, this means it will try to find that environment variable and use it (in the Heroku deployment) or if it can't (locally it's undefined
as it is not in our local .env) it'll use the local connection string.
When you do the mongolab command, the MONGODB_URI is automatically created as a environment variable on your heroku app, so it does not need to be manually added on heroku.com. You do not add this to your local .env file as it is only to be used with the deployed App with the Mongolab database.
Add a .gitignore
to your main project
create-react-app
automatically created a .gitignore
file for us and added a bunch of useful things to it. However, we want to make sure all our server stuff isn't cluttered with things too.
In your main project root, create a file called .gitignore
and add a few things we want to make sure don't end up in our repository (assuming you haven't already done this):
.DS_Store
node_modules/
.env
*.orig
.idea/
.vscode/
Most of those probably won't be necessary for you, but it doesn't hurt to add them now anyway.
Teach Heroku to run a build and run your app after deploying
You might have noticed in the React app's .gitignore
file that we're ignoring the build
folder. That's the folder that compiles everything together into one place, static assets, all JavaScript, etc. It gets created by running npm run build
. However, we don't want to commit the build
folder, which is why it's in our .gitignore
by default.
Fortunately, Heroku will look in package.json
for a script to run after we finish uploading the code there. In your server's package.json
, add the following scripts
section:
"scripts": {
"heroku-postbuild": "cd client && npm install --only=dev && npm install && npm run build"
}
Another thing we want Heroku to be able to do is run our server file with node once it's built. Heroku will look for a Heroku-specific file called a Procfile
to tell it how to do that. If no Procfile is found, it will just run npm start
. So we need to add the start
script to our scripts as well. The whole scripts section should now look like this:
"scripts": {
"start": "node server.js",
"heroku-postbuild": "cd client && npm install --only=dev && npm install && npm run build"
}
One last thing that sometimes will cause issues has to do with your version of node.js. We'll also teach Heroku which version of node to use for the project.
In terminal, type node -v
. It should spit out the version of Node.js you're using.
If that version were, for example, 8.9.2
, you'd add the following to your server's package.json
file:
"engines": {
"node": "8.9.2"
}
After all this, your server's package.json
should look something like this:
{
"name": "mern-to-heroku",
"version": "1.0.0",
"main": "index.js",
"license": "MIT",
"dependencies": {
"body-parser": "^1.18.2",
"express": "^4.16.2",
"mongoose": "^4.13.6",
"morgan": "^1.9.0"
},
"scripts": {
"start": "node server.js",
"heroku-postbuild": "cd client && npm install --only=dev && npm install && npm run build"
},
"engines": {
"node": "8.9.2"
}
}
You should now be all set up to deploy your site through Heroku!
Deploy!
Teach Heroku which Git Repo to deploy from.
In order for heroku to use your git repository as the source, you will need to go configure your github account on heroku's website.
To do this, go to heroku.com and:
- Go to your Dashboard
- Select the newly created App.
- Click the 'Deploy' tab
- In 'Deployment Method', click 'GitHub'. If you have not connected your github to heroku yet, it will have you authenticate this connect request.
- Connect your Git Repo to the Heroku app. Put the name of your git repo in the "Search for repository to connect to", then click "Connect".
Teach Heroku which branch of your Git Repo to deploy from
Once you've got everything above completed, your deployment and updating of your site should go through seamlessly. First, you will need to specify which branch of your selected repository to deploy:
IMPORTANT: Before doing this, make sure the branch you are using to deploy the application is fully updated on github and working locally when you try to start it up. It is good practice to use the
master
branch as the live version of your application.
Once you have selected a Branch, you can click 'deploy branch' and you should see some scripts running below for a minute or two.
Once finished and provided successful, a link is provided to then go visit your live website!
Updating your Heroku app
Since you are using your own Git Repo for the application, there are two simple options for updating the live version:
Option 1: Manual
- Make changes locally to your application, then
git add -A
, git commit -m, and
git pushto update the deployed
masterbranch (a pull request is then needed if this is a separate branch from the
master`). - Go to your application on Heroku.com, go to your app, click deploy, and then click 'deploy branch' for the selected branch
Option 2: Automatic
- On Heroku.com in the 'deploy' section of your application next to 'Automatic Deploys', select your deployment branch (master), and click 'enable automatic deploys'. With this enabled, your website will automatically redeploy anytime the deployment branch has any new changes via a push or pull request!
Exporting your local database to Mongolab
Mongodb has a few ways you can import/export databases, and we'll use their mongodump
and mongorestore
commands to accomplish this.
First you need to know your database name. You can find this by either:
-
Go into Mongo shell by typing
mongo
into your terminal. Then typeshow dbs
and you'll get a list of all local database names.or
-
In your server file, it is the name given in your
mongoose.connect()
method.
- eg:mongdb://localhost:27017/<db name>
- Create Export directory using
mongodump
Make sure you are in the top level of your project directory. You use the following command to make a copy of your local database and put it in a directory.
mongodump -d <db name> -o db-backup
You should see a db-backup
folder now in the project directory
- Import using
mongorestore
Mongorestore can create a new database or add data to an existing database. It does only performinserts
, notupdates
, meaning you can add new information but cannot change existing information.
Next go to mlab.com and sign in. When you created your Mongolab database, these were created for you. So you may have to request your username/password to get in.
Once logged in, you'll see a list of your deployed databases. Click on the one you want to import this database to.
Next you'll need to create a new user, separate from the one automatically made when the database was created. Click on the Users tab, then click the button to add a user.
Once the user is created, click on 'Tools'. Then click import/export, and look at the command for 'Import Database'
It should look something like this:
mongorestore -h <port/server> -d <herokuapp_name> -u <user> -p <password> <input db directory>
Use this command to do the import to the remote database.
-Keep the -h <port/server>
and -d <herokuapp_name>
the same
-Replace <user>
with the new user name you just made.
-Replace <password>
with the new password you just made
-For <input db directory>
, give the path to the dump directory.
-It will probably be something like dump-dir/db-name
Once you run this command you should have your local database uploaded to your Mongolab database!
Custom Domains
If you have purchased a domain name for your site then here is how to make your custom domain accessible via Heroku!