Continuous Integration – Automated builds between Heroku and Github

Continuous Integration – Automated builds between Heroku and Github

I am continuing my recent trend of developing projects from the ground up.  In my last article I focused on automated testing using mocha.  In this article I want to focus on how to take a MERN project and move it to production via an automated build on Heroku.  Why Heroku?  First of all, it’s cheap (as in free).  I have hosted a bit on AWS before but for this app I am hoping to actually get a higher volume of users.  I’ve heard the horror stories of people getting surprise AWS bills and I am trying to avoid that.  In a future article I’ll focus on a build to that environment.

Now I’ve done a few deploys to heroku before and I’ve done both client-side pushes where the build was local and also server builds where it grabbed the branch from github.  After spending a lifetime in a tech stack where devs could push directly to prod I can affirm the obvious – don’t build locally if you don’t have to.  Heroku and github make it extremely easy to at least start the process of continuous integration.  The devil is in the details, of course.

Lets start with the easy part.  I won’t go into the basics of setting up a heroku account (it’s simple) – I’ll just get right to the good stuff since this is more for future me.

On the Heroku “Deploy” tab you can link your github project and branch with the heroku instance.  I don’t *yet* have continuous integration service working so I’ll leave that unchecked for now.

How the other part of this setup is that I am deploying a MERN application that requires both a client-side and server-side instance.  This means that I will have multiple package.json files.  I have my client code in a “client” directory and a package.json at the root.

"build": "yarn && cd client/ && yarn install && npm run build"

I realize that this can all be done with buildpacks and if I had even a little bit more of a need to customize my build I would use them.  For now, this is fine.  So once again this is in the root package.json.  I am calling yarn in order to install the server modules that exist at this level.  There is no build at this level so I then change directory to the client folder.  I run yarn again to bring in the dependencies and then I run the build script that is in the client/package.json:

"build": "react-scripts build"

So this initiates the build using the client node_modules.  If you click over to heroku you can see the status of the build (I removed many lines to keep this readable).

-----> Building on the Heroku-20 stack
-----> Node.js app detected
-----> Creating runtime environment
NPM_CONFIG_LOGLEVEL=error
YARN_PRODUCTION=false
USE_YARN_CACHE=true
NODE_VERBOSE=false
NODE_ENV=staging
NODE_MODULES_CACHE=false
-----> Installing binaries
engines.node (package.json): unspecified
engines.npm (package.json): 6.14.10
engines.yarn (package.json): 1.x
-----> Build
Running build (yarn)
yarn run v1.22.10
$ yarn && cd client/ && yarn install && npm run build
[1/5] Validating package.json...
[2/5] Resolving packages...
success Already up-to-date.
149.87 KB (+1.9 KB) build/static/js/2.cc2fdf7f.chunk.js
5.82 KB (+54 B) build/static/js/main.9f3ad6e7.chunk.js
778 B (+8 B) build/static/js/runtime-main.d3c20ac3.js
The project was built assuming it is hosted at ./.
You can control this with the homepage field in your package.json.
The build folder is ready to be deployed.
Find out more about deployment here:
https://cra.link/deployment
Done in 127.85s.
-----> Pruning devDependencies
Skipping because NODE_ENV is not 'production'
-----> Build succeeded!

One of the other benefits of doing an automated build is that you can set the server environment to use the same setup as the dev environment.

"engines": {
    "yarn": "1.x",
    "npm": "6.14.10"
  },

Using “engines” tells Heroku to set its environment up appropriately.

This last piece is more MERN specific but I’ll include it here anyway.  Because HTTP request are going to hit the Express server before they are potentially hit the client-side router we need to account for that in the code.  In my project I have “server.js” at the project root.

app.use(routes);
app.use(express.static("client/build"));
app.get('*', (req, res=> {
     res.sendFile(path.resolve(__dirname'client''build''index.html'));
});

I have my routes (presumably all api routes) defined in their own directory and I bring them into play in the first line.  If the incoming request does not match anything in the routes file then the code will reach the next line (which of course should be after any of your server routes).  In the second line we set a new root that will be used for serving the project files.  This is the directory that includes all of your built client-side files (images, css, js, etc).  And among these files is the index.html that was creating during your build process.   The one oddity that I experienced in this area is that in my first attempt at this I had an awful time getting heroku to identify the directory properly.  I spent two full days on it and never did figure it out – I ended up creating a new project shell and with just a bit of code and I got that to work first.  I saw the others had experienced the exact same issue.

And here we have the contents of the index.html.  Note that it is built and optimized for production.

 

 

Add a comment

*Please complete all fields correctly

Related Blogs