Deploying a full stack Scala application on Heroku

Heroku and Scala logos

Project structure

A full stack Scala project is typically divided into three different sub-projects: one for the backend, one for the frontend, and a third one on which the two first depend and that will therefore cross-compile for JS and for the JVM.

build.sbt
- project
- backend
-- src
- frontend
-- src
-- webpack
- shared
-- src

Compiling the frontend

Following the great work done by Slinky, the frontend uses webpack and scala-js-bundler for development and build purposes. We get a nice “hot reload” functionality when coding (thanks to the dev alias command) and a simple frontend/fullOptJS/webpack task to build the frontend ready for production, by outputting the packages files to the backend’s public folder.

Create the app

Creating a new application is extremely easy with the Heroku CLI. In our case, we want to create a simple application, together with a free Postgres database. This database is limited to 10000 lines (which should not give you the idea to transpose your tables).

heroku apps:create APP_NAME --region eu
heroku addons:create heroku-postgresql:hobby-dev

The sbt-Heroku plugin

A Heroku application can typically be deployed by sending the code and let Heroku manage compilation and deployment. And that is possible for 8 different programming languages and at least that many web frameworks. However, for Scala applications, there is another route which is through the “sbt-heroku” plugin. This plugin gives us the ability to package the application ourselves (which is good in this case since we need to package the frontend anyway) and then send the packaged output to Heroku so that it can deploy the app for us.

addSbtPlugin("com.heroku" % "sbt-heroku" % "2.1.3")
lazy val `backend` = (project in file("./backend"))
.enablePlugins(PlayScala) // for example
.settings(
herokuAppName in Compile := "APP_NAME",
herokuProcessTypes in Compile := Map(
"web" -> "target/universal/stage/bin/backend -Dhttp.port=$PORT" // command for Heroku to launch the server
//"worker" -> "java -jar target/universal/stage/lib/my-worker.jar"
),
herokuIncludePaths in Compile := Seq(
"backend/app",
"backend/conf/routes",
"backend/conf/application.conf",
"backend/public"
),
// [...]
)
.dependsOn(sharedJVM)
  • the frontend does not build into the backend public directories, and
  • you will see that the task backend/deployHeroku finishes with “success” but nothing actually happened.
stage := {
val webpackValue = (frontend / Compile / fullOptJS / webpack).value
println(s"Webpack value is $webpackValue")
(stage in backend).value
}

Accessing the database

We created an app with a Postgres database. To connect to it, we need to have, for example, the JDBC url. In Heroku, any application provided with a Postgres database has an environment variable called JDBC_DATABASE_URL . If you use the configuration files from Typesafe, you can for example retrieve it with

url = ${?JDBC_DATABASE_URL}

Specific to Play

If you are using the Play framework, here are some small random remarks that could serve as a FAQ:

  • in production, you need to define an application secret. You can easily do that by setting an environment variable on your Heroku app, for example via heroku config:set APPLICATION_SECRET=mycoolsecret and then in the configuration file, use play.http.secret.key = ${?APPLICATION_SECRET}
  • your application in Heroku lives in a container, so the origin actually accessing your app is probably unknown. Play will reject incoming connections unless you set play.filters.host.allowed = ["."] .
  • another consequence on having the application living in a container is that you need to know the port on which you traffic will pass. On Heroku, you receive an environment variable PORT which contains that piece of information
  • you most likely want to enable auto evolutions of the database. This is done via play.evolutions.autoApply = true
  • in dev mode, you can create a configuration file with dev values for all the afore-mentioned variables, such as the db url

Wrap up

Hopefully this post can save you the hassle to collect all the small bits of information needed to put all of this together. Admittedly, this post is also a letter to me in the future, but I figured I could share it!

--

--

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