Using a monolith repository with Heroku

Nowadays, most of the projects (and/or products) are going towards splitting responsibilities in multiple applications. You will have a pure-JavaScript (ish, could be compiled using things like Elm) front-end and a backend API in any programming language.

How can you deploy your front-end and backend applications from a monolith repository to Heroku?

If you use different Git repositories for each of these applications you won’t have any problem. But I have to admit, I’m usually an advocate of monolith repositories. If you want to know why, this article is a good summary.

Image for post
Image for post
Art of monolith repository

Using different Heroku applications

In order to achieve this goal, you could use different Heroku applications and deploy these applications by running the heroku commands in the right directory from your continuous integration pipeline. This would work but there are multiple drawbacks:

  • You will have some manual configuration. Typically, how does the front-end application know the address of the backend application? You’ll have to manually configure the URLs. It might not be a big pain but it will slow you down if you want to create another environment or forces you to write some scripts if you want to use Review Apps, to have a deployment per pull-request.
  • You probably don’t need multiple applications. The only “benefit” of having multiple applications is to scale them independently (and you might not even need this). Multiple applications increases the cost, the operational complexity (you can’t attach all the add-ons to multiple applications for example) and reduces the automation (your Heroku Button won’t work for multiple applications).
  • You won’t use Heroku’s GitHub integration. That might seems a non-problem but being able to see what are the diffs being deployed might be very valuable.

Instead, you can deploy your different front-end/backend applications within the same Heroku application buy using multiple build packs.

Multiple applications to a single Heroku application

First of all, did you know Heroku supports multiple buildpacks? On top of that, Mark Miyashita built a buildpack that supports different buildpacks in different sub-folder of your repository.

Let’s take the example of a React front-end and a Symfony backend. The backend will require a database. We will get started by writing an app.json file to automate the creation of the application and its add-ons:

# app.json
{
"buildpacks": [
{
"url": "https://github.com/negativetwelve/heroku-buildpack-subdir"
}
],
"addons": [
{
"plan": "heroku-postgresql",
"options": {
"version": "9.5"
}
}
],
"env": {
"NPM_CONFIG_PRODUCTION": {
"value": "false"
}
},
"formation": {
"web": {
"quantity": 1,
"size": "hobby"
}
}
}

Configure the buildpacks for each application

Now, to use the heroku-buildpack-subdir buildpack, you need to define which buildpack will be used into which directory in a .buildpacks file at the root of the repository. We imagine that you will have the following folder structure in your Git repository:

frontend/
src/
...
package.json
api/
src/
...
composer.json
.buildpacks

This .buildpacks file contains the following “folder-buildpack mapping”:

frontend=https://github.com/heroku/heroku-buildpack-nodejs.git#v118
api=https://github.com/heroku/heroku-buildpack-php.git#v131

Both applications needs to be accessible from the web

An Heroku application can have multiple dynos. But there is one restriction: only the web dyno is accessible from the Internet. In order to make this work we will configure nginx (shipped within PHP’s buildpack) that way:

  • Everything starting with/api to the PHP backend
  • The rest of the traffic will be wildcarded to the /index.html file (i.e. the built single page application) unless the file exists.

The nginx.conf configuration file matching this requirement looks like that:

location ^~ /api/ {
alias /app/api/public/;

if (!-e $request_filename) {
rewrite ^ /api/index.php last;
}

location ~ \.php$ {
include fastcgi_params;

fastcgi_split_path_info ^(.+\.php)(/.*)$;
fastcgi_param SCRIPT_FILENAME $request_filename;
fastcgi_param PATH_INFO $fastcgi_path_info if_not_empty;

fastcgi_pass heroku-fcgi;
internal;
}
}

# Set the cache headers
location = /index.html {
expires 1s;
}

location ~* \.(?:ico|css|js|gif|jpe?g|png)$ {
expires 30d;
}

# Front-end files
location ~ ^/ {
try_files $uri @filedonotexists;
}

# If the file do not exists, we use the SPA index page
location @filedonotexists {
try_files $uri /index.html;
}

When customising anything, in dockerfiles or Heroku’s buildpacks, I quite like clarifying how the plumbing works by creating a decidated startup script. I’ll write a heroku/start bash script that will run API’s nginx with our custom configuration:

#!/bin/bash
set -xe

# Start Heroku's PHP + nginx buildpack
pushd api
exec ./vendor/bin/heroku-php-nginx -C ../heroku/configuration/nginx.conf ../frontend/dist/

Last, but not least, configure your web dyno to use this script in a Procfile at the root of the code repository:

web: ./heroku/start

Now, all you need to do is to push this configuration in your favourite code repository and go the the following URL to deploy your application:

Happy monolith!

Written by

Software Engineering. Containers. APIs & IPAs.

Get the Medium app

A button that says 'Download on the App Store', and if clicked it will lead you to the iOS App store
A button that says 'Get it on, Google Play', and if clicked it will lead you to the Google Play store