Running ClojureScript Apps on Heroku
In this article, you’ll create a simple ClojureScript application with Leiningen, Figwheel, Om and a few other libraries from the Chestnut template. Then you’ll learn how to deploy that application as an Uberjar to Heroku.
Creating a ClojureScript app
The simplest way to create a new ClojureScript app is by using a template such as Chestnut. Chestnut builds a barebones app that is prepared for development with a browser-connected REPL and instant reloading of Clojure, ClojureScript, and CSS. But it also has all of the configuration you need to deploy that same app to production.
To create a new Chestnut application, run this command:
$ lein new chestnut my-app
Then move into the app directory by running:
$ cd my-app
Now you can run the application locally from a REPL. First, start the REPL by running this command:
$ lein repl
nREPL server started on port 59194 on host 127.0.0.1 - nrepl://127.0.0.1:59194
REPL-y 0.3.7, nREPL 0.2.10
Clojure 1.6.0
Java HotSpot(TM) 64-Bit Server VM 1.8.0_51-b16
Docs: (doc function-name-here)
(find-doc "part-of-name-here")
Source: (source function-name-here)
Javadoc: (javadoc java-object-or-class-here)
Exit: Control+D or (exit) or (quit)
Results: Stored in vars *1, *2, *3, an exception in *e
my-app.server=>
When the REPL is ready, execute the (run)
function to start the app:
my-app.server=> (run)
Figwheel: Starting server at http://localhost:3449
Starting web server on port 10555.
2015-10-15 10:34:40.132:INFO:oejs.Server:jetty-7.6.13.v20130916
2015-10-15 10:34:40.173:INFO:oejs.AbstractConnector:Started SelectChannelConnector@0.0.0.0:10555
#<Server org.eclipse.jetty.server.Server@3888ab53>
my-app.server=> Compiling "resources/public/js/app.js" from ["src/cljs" "env/dev/cljs"]...
Successfully compiled "resources/public/js/app.js" in 10.023 seconds.
notifying browser that file changed: resources/public/js/app.js
notifying browser that file changed: resources/public/js/out/goog/deps.js
notifying browser that file changed: /my_app/core.js
notifying browser that file changed: /my_app/main.js
After you see the Successfully compiled ...
message, open a browser to http://localhost:10555
, and you’ll see the “Hello Chestnut” text.
Preparing for Production
The Chestnut template is already prepared for Heroku deployment. In this section, we’ll describe how it’s configure so that you can use a similar pattern in your own application if you didn’t start with Chestnut.
When you deploy a Clojure or ClojureScript application to Heroku, the platform detects if your application is configured to be built as a Uberjar. You may be using Figwheel in development, but in most cases you will want the robustness of a Jetty or HttpKit backed Uberjar for deployment.
A Chestnut application will contain this line in it’s project.clj
:
:uberjar-name "my-app.jar"
It also contains a profile that configures how the Uberjar will package up the Clojure and ClojureScript code:
:uberjar {:source-paths ["env/prod/clj"]
:hooks [leiningen.cljsbuild]
:env {:production true}
:omit-source true
:aot :all
:main my-app.server
:cljsbuild {:builds {:app
{:source-paths ["env/prod/cljs"]
:compiler {:optimizations :advanced
:pretty-print false}}}}}})
The main entry point of the Uberjar is my-app.server
, which is a file that was generated by Chestnut. This file contains a main
function that boots Ring and Jetty, which provide an HTTP server for the static content compiled from your ClojureScript code. This is especially convenient because in most cases you’ll eventually need some server-side logic, which you can easily add to the project.
Before you deploy, try building the Uberjar locally:
$ lein with-profile -dev,+production uberjar
And run it with this command:
$ heroku local web
The heroku local
command reads the web
command from the application’s Procfile
. This file is preconfigured by Chestnut with the following content:
$ web: java $JVM_OPTS -cp target/my-app.jar clojure.main -m my-app.server
You’ll need to add a similar file to a non-Chestnut app before deploying.
Open a browser to http://localhost:5000
and you’ll see the “Hello Chestnut” text again. Then kill the server by pressing Ctrl+C.
Deploying to Heroku
Before you can deploy to Heroku, you’ll need to create a free Heroku account and install the Heroku toolbelt. Then log into the toolbelt CLI with the account you created by running:
$ heroku login
Next, initialize a local Git repository for your application by running these commands:
$ git init
$ git add .
$ git commit -am "first"
Now create a Heroku application for your project by running this command:
$ heroku create
This will provision a new remote Git repository on the Heroku servers, and add it as a remote to your local repo. To deploy, you push to the remote Git just as you might push to a Github repo by running this command:
$ git push heroku master
...
remote: -----> Clojure (Leiningen 2) app detected
remote: -----> Installing OpenJDK 1.8...done
remote: -----> Installing Leiningen
remote: Downloading: leiningen-2.5.2-standalone.jar
remote: Writing: lein script
remote: -----> Building with Leiningen
remote: Running: lein uberjar
...
remote: -----> Compressing... done, 72.5MB
remote: -----> Launching... done, v3
remote: https://limitless-island-1563.herokuapp.com/ deployed to Heroku
remote:
remote: Verifying deploy... done.
To https://git.heroku.com/limitless-island-1563.git
* [new branch] master -> master
After some time, your app with be up and running. You can check it’s status with the heroku ps
command, and you can view it’s logs with the heroku logs
command. To view the app in a browser, run this command:
$ heroku open
Pushing Local Changes
Now let’s make some changes to the application and push those up to Heroku.
Open the file src/cljs/my_app/core.cljs
. It looks like this:
(ns my-app.core
(:require [om.core :as om :include-macros true]
[om.dom :as dom :include-macros true]))
(enable-console-print!)
(defonce app-state (atom {:text "Hello Chestnut!"}))
(defn main []
(om/root
(fn [app owner]
(reify
om/IRender
(render [_]
(dom/h1 nil (:text app)))))
app-state
{:target (. js/document (getElementById "app"))}))
As you can see, it’s using Om, a ClojureScript interface to Facebook’s React. Let’s change this simple view so that it renders a list of contact. In the spirit of Clojure, you’ll use REPL-driven development as you do this. Before starting you’re REPL, you need to clean the Uberjar artifacts by running this command (otherwise you’ll get a “Production environment code is being loaded while the dev environment is active” error).
$ lein clean
Now start your REPL again and boot the app with (run)
. In the src/cljs/my_app/core.cljs
file, replace the app-state
definition with this code:
(defonce app-state
(atom
{:contacts
[{:first "Ben" :last "Bitdiddle" :email "benb@mit.edu"}
{:first "Lem" :middle-initial "E" :last "Tweakit" :email "morebugs@mit.edu"}]}))
Then add these functions after the app-state definition:
(defn display-name [{:keys [first last] :as contact}]
(str last ", " first))
(defn contact-view [contact owner]
(reify
om/IRender
(render [this]
(dom/li nil (display-name contact)))))
(defn contacts-view [data owner]
(reify
om/IRender
(render [this]
(dom/div nil
(dom/h2 nil "Contact list")
(apply dom/ul nil
(om/build-all contact-view (:contacts data)))))))
Finally, replace the main
function with this:
(defn main []
(om/root
contacts-view
app-state
{:target (. js/document (getElementById "app"))}))
Then view your app in the browser and you’ll see the list of contact.
Now you can deploy these changes to Heroku with just a few commands. Kill the REPL or open a new terminal and run this:
$ git commit -am "contacts"
$ git push heroku master
In just a few moments you app with be deployed and you can view it again with heroku open
.
This is just a very simply introduction to Om and ClojureScript. You can learn a great deal more about unlocking their potential in the Om tutorial.
Further Reading
Here are some articles on Heroku’s Clojure support:
- Deploying Clojure Apps on Heroku
- Heroku Clojure Support
- Deploying Clojure Applications with the Heroku Leiningen Plugin
- Deploying Clojure Apps to Heroku with Docker
Here some great resources for learning more about ClojureScript.