Deploying your MERN project with Heroku
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."
This will send you a confirmation email, so check your email and click the link to activate your account. This will take you to a page where you create and confirm a password. Do so, and click "Set password and log in"
2. Download and install Heroku CLI
Heroku CLI is a command-line tool that makes it easy to keep your web app updated. This is what allows you to use git to push your code up and have your website be updated instantly!
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.
4. Create a new application
Creating a Heroku application is like creating a space online for your content to live. It helps to think of it like a GitHub repository - it's a space online that will hold your code, and when you make a change to your code, you simply use git to push your new code up to Heroku. The main difference is that while GitHub just stores your actual code, Heroku will actually deploy your code to a website, so it's ready for someone to look at.
Heroku allows you to do most of the configuration for any of your applications either 1. through the dashboard on their website, or 2. 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.
We'll use the Heroku CLI to create a new app for us. One thing that's nice about using the CLI to create the app is that it will also set up a new git remote for you and save you a step in the future.
Your app will need to have a unique name across all of Heroku. Usually if you name it something like "yourname-todos"
it'll work, unless you've got a really common name.
From your project root folder, run:
$ heroku create name-of-your-app
As mentioned above, this command creates a new Heroku app (if you refresh your Heroku dashboard it should show up now), as well as setting up the new git remote for pushing your code to Heroku.
Until now, you've likely only ever referred to one git remote before, traditionally called "origin" from our local machine. (git push origin master
sound familiar?). If you followed the commands above, Heroku just created a link to a remote repository and called it heroku
. When the times comes to deploy, you'll run git push heroku master
to deploy your app!
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.
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 setups
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.
Modify any API URLs necessary
If you've been using something like "http://localhost:8000/api/todos"
as the URL for your API calls (since create-react-app
ran your app on a development server on port 3000 but your server was running on a different port like 8000), you can simplify your URL by removing the whole origin section from the beginning. So "http://localhost:8000/api/todos"
should be changed to just "/api/todos"
.
Since your server will be running on the same machine and port as your React app, the whole beginning part will be assumed.
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 within the Heroku context so it knows which values to use when the project is running in Heroku. You can do this two ways:
- Set them up on the website
- Set them up from the CLI
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!
Setting environment variables from the CLI:
- In Terminal, type
heroku config:set VAR_NAME_GOES_HERE="value goes here"
- Hit enter. 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/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/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 use that in your code where you connect to the database:
mongoose.connect(process.env.MONGODB_URI || "mongodb://localhost/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
) it'll use the local connection string.
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!
Once you've got everything above completed, your deployment and updating of your site should go through seamlessly. You'll use git to create a new commit and push to the heroku
remote:
$ git status
$ git add -A
$ git commit -m "Add a commit message here. Probably 'initial commit' would be fine for the first time"
$ git push heroku master
Pushing to Heroku takes a little longer than pushing code to GitHub, so give it a few extra seconds. If everything went correctly, it should show a success message and tell you where your app is deployed, which is at https://<your-app-name>.herokuapp.com/
Updating your Heroku app
Any time you make changes to your application, all you have to do to update the live site is run your usual git add -A
and git commit -m "message"
, but instead of pushing to your GitHub account, you'll just run git push heroku master
instead. This will push your new code to your running Heroku instance and relaunch the site with your changes.
Custom Domains
If you have purchased a domain name for your site then here is how to make your custom domain accessible via Heroku!
Export/Import Database
Most projects will be fine without this step, but if you really want all your data that you have while you've been developing, you can export your local database, and import it to MongoLab.
(1. know your database name.
There are two ways to find this.
Get into the Mongo shell by typing mongo
into terminal
type show dbs
into the shell. Find yours.
It's also in the line in your server file in your project:
mongoDB://localhost/<db name>
(2. Export
mongodump -d=<db name> -o=db-backup
cd into db-backup
(3. Import
Go to heroku.
, settings, reveal config vars, one for mongodB_uri
Import database line in terminal
In her
overview
mlab mongoDV (installed addon)
will open stuff mLab
When you added the addon, it did everything for you. (uname and pword)
Open into your account
far right, tools
copy the import line.
mongodb://
You will need to click edit