How to deploy a create-react-app with an Express backend to Heroku
You used the create-react-app cli tool to create your React app, and now you want to add a backend such as Express. How do you do it?
There are several ways to go about it:
Host them separately: Deploy the Express app on one machine, and the React app on another machine.
Use a proxy: Both apps are hosted together, but served by different servers.
Keeping them together: Have your frontend React files and backend Express files exist on one server, and have Express serve your React files in addition to the API requests.
Here, we will focus on the third option. Together, we will create a simple app that uses Express to create a backend API, and also serve React files. Then we will deploy it on Heroku. If you don’t have a Heroku account, sign up for free! In addition, install the Heroku cli tools. (Alternatively, just $ brew install heroku)
Let’s create our app!
The Backend Express app
First we’ll create our root directory. I’ve named mine crae-example but feel free to be creative. Then, initialise it with NPM or Yarn.
$ mkdir crae-example
$ cd crae-example
$ npm initInstall packages with NPM or Yarn, then make our index file.
$ npm i express cors cowsay
$ touch index.jsWe want to create our Express server to serve two routes. A basic route that will return a cow saying ‘Hello World!’, and one that accepts custom input. Since it’s a simple app, we’ll keep everything in one file, although in practice it is much better to modularise.
const express = require('express')
const cowsay = require('cowsay')
const cors = require('cors')// Create the server
const app = express()// Serve our api route /cow that returns a custom talking text cow
app.get('/api/cow/:say', cors(), async (req, res, next) => {
try {
const text = req.params.say
const moo = cowsay.say({ text })
res.json({ moo })
} catch (err) {
next(err)
}
})// Serve our base route that returns a Hello World cow
app.get('/api/cow/', cors(), async (req, res, next) => {
try {
const moo = cowsay.say({ text: 'Hello World!' })
res.json({ moo })
} catch (err) {
next(err)
}
})// Choose the port and start the server
const PORT = process.env.PORT || 5000
app.listen(PORT, () => {
console.log(`Mixing it up on port ${PORT}`)
})That’s it for our index.js. Let’s add a start script to our package.json and test out our single route.
"scripts": {
"test": "echo \"Error: no test specified\" && exit 1",
"start": "node index.js"
},Alternatively, you can use nodemon index.js to listen for any changes. Let’s start our app:
$ npm startYou should see the console log in your terminal.

Then navigate to http://localhost:5000/api/cow. It’s gibberish, but it is our moo-ing text cow!

Deploying to Heroku
It is good practice to deploy in the beginning, make sure everything works, then keep adding. Let’s initialise a git repository so we can deploy to Heroku.
$ git init
$ echo node_modules > .gitignore
$ git add -A
$ git commit -m "First commit"Assuming you have installed Heroku’s CLI tools, we can now use it for deployment. If you haven’t already, login to Heroku.
$ heroku login // enter in your credentials
$ heroku create crae-example
Now we just need to push our code up to Heroku.
$ git push heroku master
If we go to https://crae-example.herokuapp.com/api/cow we should see the same gibberish output as above. Our cow (app) is now live on the internet!
The Frontend React App
We need a frontend to show our cow don’t we? Let’s install the create-react-app cli tool.
$ npm i -g create-react-appInside our root directory that houses our Express app, let’s create our frontend app:
$ create-react-app clientSince that in of itself is an app, we need to proxy API requests from the frontend app to our backend Express app. Luckily that is easily done by adding the following in client/package.json .
"proxy": "http://localhost:5000"What that does is when we call on /api/cow from the client side, instead of going to http://localhost:3000/api/cow, it will proxy us to http://localhost:5000/api/cow.
Next, we’ll replace src/App.js with the following:
import React, { Component } from 'react'
import './App.css'class App extends Component {
state = {
cow: '',
text: ''
}componentDidMount() {
this.fetchCow()
}fetchCow = async () => {
const response = await fetch(`/api/cow`)
const initialCow = await response.json()
const cow = initialCow.moo
this.setState({ cow })
}customCow = async evt => {
evt.preventDefault()
const text = this.state.text
const response = await fetch(`/api/cow/${text}`)
const custom = await response.json()
const cow = custom.moo
this.setState({ cow, text: '' })
}handleChange = evt => {
this.setState({ [evt.target.name]: evt.target.value })
console.log(this.state.text)
}render() {
return (
<div className="App">
<h3>Text Cow. Moo</h3>
<code>{this.state.cow}</code>
<form onSubmit={this.customCow}>
<label>Custom Cow Text:</label>
<input
type="text"
name="text"
value={this.state.text}
onChange={this.handleChange}
/>
<button type="submit">Show me a talking cow!</button>
</form>
</div>
)
}
}export default AppIt looks like a lot, but it isn’t! We simply call fetchCow in our componentDidMount which will give us our ‘Hello World’ cow and set it on state so we can display it on render. We also render an input form that lets users type in their custom string text for the cow output.
Optional: update src/App.css I recommend at least adding white-space: pre, which will keep all the white spaces instead of compressing multiple white spaces into one. It’s very important for the cow!
.App {
display: flex;
flex-direction: column;
align-items: center;
margin: 2em;
white-space: pre
}.App code {
margin: 2em;
margin-top: 0;
padding: 0;
text-align: left;
font-size: 2em;
}h3 {
font-size: 2.5em;
margin: 1em;
padding: 0;
border: 0
}button {
font-size: 1em;
font-family: "Courier New", monospace;
border: 2px solid #000;
padding: 10px 25px;
}button:hover {
background-color: rgb(255, 235, 59);
}button:active {
background-color: rgb(255, 244, 142);
}form {
display: flex;
flex-direction: column;
font-family: "Courier New", monospace;
}input {
font-size: 1.5em;
margin-top: 1em;
margin-bottom: 1em;
border: 1px solid grey
}Since this is a simple app, we define css attributes directly on elements instead of specifying classes (not recommended). To test that everything works, we have to start both our apps (from the root directory):
$ npm run start
$ cd client
$ npm run startGo to http://localhost:3000 and you should see this:

One last thing: we need to update our index.js in our root directory to serve up our React files once it’s in production:
const path = require('path')// Serve static files from the React frontend app
app.use(express.static(path.join(__dirname, 'client/build')))// Anything that doesn't match the above, send back index.html
app.get('*', (req, res) => {
res.sendFile(path.join(__dirname + '/client/build/index.html'))
})Final Takeoff
Before we can deploy again, we first need to configure Heroku to build the frontend React app after we push all our code up. This is because we explicitly specified above to serve static files from client/build. Let’s configure our root directory’s package.json one last time:
"scripts": {
"test": "echo \"Error: no test specified\" && exit 1",
"start": "node index.js",
"heroku-postbuild": "cd client && npm install && npm run build"
},We add a heroku-postbuild script so it knows to run the built in build method create-react-app gives us after Heroku is done doing its own build. Now we can deploy!
$ git add -A
$ git commit -m 'Deploy takeoff!'
$ git push heroku masterA bunch of stuff will happen in the terminal. but at the end you should see the same url as before. Go to your Heroku url and see your app running! You can play with mine at https://crae-example.herokuapp.com/. Congratulations! You have a React and Express app in production!
Check out the code here on Github. Happy coding! : )






