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.
Prerequisites for this tutorial are:
- Heroku account has been created
- Heroku toolbelt is installed
- Leiningen 2.x is installed
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!