So you organized the code of your application in a monorepo, as all the cool kids do nowadays. You probably have one or multiple single page applications, some backend API and shared libraries there.
You may be using yarn workspaces
with all the apps and libraries gathered in directories under packages/*
.
Now you want to deploy all of that to heroku. The first natural problem: Heroku usually operates under a one application per repository premise.
The first solution for deploying multiple apps from a single repository are custom buildpacks like heroku-buildpack-multi-procfile or heroku-buildpack-monorepo.
They all work very similar: they allow you to store multiple apps in sub-directories in your git repo. On build, they either copy the Procfile
or the full sub-directory to the root. Which file or sub-directory to choose is provided via an environment variable.
After this copying, Heroku would pick up the Procfile
or start
/build
scripts from package.json
- as with every other application.
Easy enough.
But: this approach has issues with actual monorepos.
- Copying everything under
packages/something
to the root of the folder might break configuration files for typescript, eslint, prettier etc. already existing there - and it would overwritepackage.json
in the root. - Depending on the hoisting behaviour of
node_modules
, just copying ayarn workspace
into a different directory can break your dependency resolution. - The same might happen for dependencies between workspaces. E.g.
packages/package-a
depending onpackages/package-b
and being linked together.
There's an easier way
Imagine your repository has an api, frontend and shared library:
/ <root>
- package.json
- /packages
- /api
- package.json
- /frontend
- package.json
- /shared
- package.json
With this layout, you can use the scripts
section of the root package.json
to run commands in the individual workspaces:
{
"name": "workspace-root",
"scripts": {
"start": "yarn workspace api run start"
},
"workspaces": ["packages/*"]
}
This would run the start
script in packages/api/package.json
(assuming the name
in that file is set to api
).
These scripts interpolate environment variable, so we can set one which tells us, what workspace to run the command in:
"scripts": {
"start": "yarn workspace $WORKSPACE_NAME run start"
},
Now you just create two apps from the same repo using different values for that environment variable:
# Create the api app using the api workspace
heroku create api --remote heroku-api
heroku -a api config:set WORKSPACE_NAME api
# Create the frontend app using the frontend workspace
heroku create frontend --remote heroku-frontend
heroku -a frontend config:set WORKSPACE_NAME frontend
That's all. Now you're running multiple apps from the same repository without even using a special buildpack or messing around with your folder layout.
There's more
The same works, of course, for all the other scripts (build
, heroku-postbuild
etc) Heroku uses during the build/release cycle.
You can also use it for scripts like release
which are not available via package.json
scripts only. Just create a Procfile
which delegates the work to package.json
:
Procfile
release: yarn release
package.json
"scripts": {
"start": "yarn workspace $WORKSPACE_NAME run start",
"release": "yarn workspace $WORKSPACE_NAME run release"
},
Remember: keep things simple ;)