Clojure in the cloud. Part 2: Heroku

This is the second post in a group of tutorials on deploying Clojure applications on various cloud platforms.

All posts in the series:

Heroku is a PaaS provider that supports multiple languages, including Clojure. Therefore, deploying a Clojure application to Heroku is a rather simple task.

Heroku
image: Heroku

Prerequisites for this tutorial are:

In this tutorial we will create a simple movie title search page and deploy it to Heroku.

Build the application

Let’s use the leiningen Heroku template to create a project structure.

$ lein new heroku flowa-movie-search

Pick any name you like as long as it is not flowa-movie-search since that one is already taken. ;)

Now the project looks like this:

├── Procfile
├── project.clj
├── README.md
├── resources
│   ├── 404.html
│   └── 500.html
├── src
│   └── flowa_movie_search
│       └── web.clj
└── test
    └── flowa_movie_search
        └── web_test.clj

This is a structure for a basic ring/compujure application except for Procfile. It is a text file that tells Heroku which command to run on the server when deploying the application.

In our case it looks like this:

web: java $JVM_OPTS -cp target/flowa-movie-search-standalone.jar clojure.main -m flowa-movie-search.web

This file declares a process type web. It means that the application will use the Heroku HTTP routing stack.

Store the application in Git

$ git init
$ git add .
$ git commit -m init

Run and test locally

$ lein run -m flowa-movie-search.web 7000

Now point your browser to localhost:7000 and you should see: [“Hello” :from Heroku]

Code away!

If you are only interested in dealing with Heroku, feel free to jump straight to deploying to Heroku.

Now that we have everything in place, let’s do some coding. We will implement a simple search page where you can find movies by their title. We will use OMDBApi for the movie data.

We will modify 3 clojure files. Two of these were created by the leiningen plugin:

  • project.clj
  • web.clj

And one file we will create ourselves:

  • layout.clj

The file structure will look like this:

├── Procfile
├── project.clj
├── README.md
├── resources
│   ├── 404.html
│   ├── 500.html
│   └── public
│       └── bootstrap.min.css
├── src
│   └── flowa_movie_search
│       ├── views
│       │   └── layout.clj
│       └── web.clj
└── test
    └── flowa_movie_search
        └── web_test.clj

You may have noticed that a css file was also added. It is there only to make the outcome a bit prettier.

Let’s take a look at the code.

project.clj

We’ll add two new dependencies. The templating library Hiccup and the HTTP client library clj-http respectively.

[hiccup "1.0.5"]
[clj-http "0.9.1"]
web.clj

Let’s modify the route definition first.

(defroutes app
  (route/resources "/")
  (ANY "/repl" {:as req}
       (drawbridge req))
  (GET "/" []
       (layout/common (layout/search-page)))
  (POST "/" request (result-page request))
  (ANY "*" []
       (route/not-found (slurp (io/resource "404.html")))))

At line 2 we enable the serving of resources. The root directory defaults to public. Since our css file is in resources/public we are good.

Then we replace the hello world in GET “/” route with a call to our implementation of the search page and add a new route POST “/”, which handles the user input.

The form handler function result-page queries the OMDBApi with the user input and then passes the JSON results to the search page, which knows how to render them.

(defn result-page [{:keys [form-params]}]
  (let [title (get form-params "search-string" "")
        fetch (fn [search-str] (client/get "http://www.omdbapi.com/"
                                     {:query-params {:s search-str}
                                      :accept :json
                                      :as :json}))
        render-result (fn [json] (layout/common (layout/search-page json)))]
    (render-result (:body (fetch title)))))
layout.clj

Here we implement the search page using the templating library Hiccup. Checkout the App Engine post for a more detailed explanation on Hiccup.

(ns flowa-movie-search.views.layout
  (:require [hiccup.page :refer [html5 include-css]])
  (:require [hiccup.form :refer [form-to]]))

(defn- custom-input [type name placeholder]
  [:div
   [:input {:type type :name name :placeholder placeholder :style "float:left"}]])

(defn- submit-btn [text]
  [:button {:id "submit" :class "btn btn-primary"} text])

(defn- movie-list [{entries :Search}]
  [:div
   [:h4 "Found:"]
   (for [{title :Title year :Year} entries]
     [:div (str title " (" year ")")])])

(defn common [& body]
  (html5
    [:head
     [:title "Flowa Heroku demo"]
     (include-css "bootstrap.min.css")]
    [:body body]))

(defn search-page [& [results]]
  [:div
   [:div {:class "well"}
    [:h4 "The amazing Flowa movie search"]
    (form-to [:post "/"]
             (custom-input "text" "search-string" "Movie title")
             (submit-btn  "Search"))]
    (if results
     (movie-list results))])

There are two “public” functions: common and search-page. The first one creates an HTML document from the content and the latter creates the form and shows the results if a results collection is given as an argument. The function movie-list iterates over the collection and creates an HTML representation for the data.

The result JSON is represented as a Clojure map. OMDBApi uses the following format:

{"Search":
[{"Title":"FooTitle","Year":"1964","imdbID":"tt111111","Type":"movie"}
 {"Title":"BarTitle","Year":"1982","imdbID":"tt222222","Type":"movie"}]}

Deploy to Heroku

Create a new Heroku app

$ heroku login

Ie. login to heroku from the CLI. You will need to enter your Heroku credentials.

Then let’s create the app.

$ heroku apps:create flowa-movie-search

Now the app should be listed on the web dashboard. This also adds a new remote repository (heroku) to your git config. You can check your git config at <repo-dir>/.git/config.

Deploy

If you haven’t already committed the changes to your local repository, now it’s time to do it.

$ git add -A
$ git commit

Then we can push the code to the remote repository.

$ git push heroku master

Note that if you are working locally on a branch other than master use $ git push heroku your-branch:master

Start a dyno

In Heroku, apps are run in virtual containers called dynos. We need to ensure that there is at least one dyno running the process type web which we declared in our Procfile.

$ heroku ps:scale web=1

Check the state of the app’s dyno:

$ heroku ps

Open the page in the browser

$ heroku open

…or point your browser at http://<your-app-name>.herokuapp.com.

And that should do it! Happy hacking with Clojure and Heroku!

Tero Kadenius is a software developer, ScrumMaster and a self proclaimed change agent with years of experience in the industry. Tero is the chairman of Agile Jyväskylä Ry, the non-profit behind the AgileJkl conference. Tero has a background in Java based technologies. Currently Tero finds functional programming a great tool for solving hard problems.

comments powered by Disqus