Skip to content

XW

Basic Web Application in Clojure

Abstract: This tutorial covers the creation of a basic CRUD web application (read: blog) using Clojure. The technologies covered here include Compojure, Ring, Hiccup, JDBC, MySQL, Apache, and Tomcat. Development is done on Mac OS X 10.8.5, and deployment is done on a VPS with Ubuntu 12.04 installed.

Table of Contents

Introduction

If you want to learn a language that’s expressive and that will stand the test of time, a functional language is probably the way to go. At least, that’s the case Paul Graham seems to be making. Some even say that understanding Lisp is like achieving enlightenment.

So I decided to give it a try. Between the functional languages Haskell and Clojure, I ended up picking Clojure because of its practicality, growing popularity, and because it runs on JVM, which means, pretty much everywhere.

But because Clojure is still young, the documentation could still use a lot of improvement. There are quite a few articles on how to create a web application using Clojure, but they all cover different combinations of libraries. And because it is a young language, packages are still in flux (e.g., Noir has been deprecated, but it has also been incorporated into the Compojure), so articles can quickly become outdated. All of these things can easily overwhelm a newcomer like me, and cause one to spend hours on tangential results on Google. So I’m joining the fray to make an already messy landscape even more messy. Just kidding. What I really hope to achieve here is to create a guide that I would have appreciated when I started out (about a week ago).

Caveat

Since I just started using Clojure, I probably don’t have best practices down. Furthermore, I didn’t fully DRY out my code, mostly because adding additional layers of abstraction can be confusing to a beginner. In general, as good practice, try to decompose the functions as much as possible.

Objective

The goal is to help new users of Clojure set up a simple CRUD web application from the ground up (or nearly so). Because I want people to get a feel for what making a Clojure web app is like, I won’t focus on optimization, validation, tests, or security. Once you grasp how everything fits together, I have no doubt you will be able to implement those things on your own.

This tutorial will try to keep code and explanations as short as sufficient and as long as necessary. I will assume you have basic knowledge of Clojure syntax (i.e., you know the difference between a list and a vector, you know what #(myfunction %) means, you know basic Clojure functions like map, str, etc.

Environment

I’m using Mac OS X 10.8.5 for development, and a VPS running Linux (Ubuntu 12.04) for production. You can use any text editor you want. If you want a REPL without dealing with configuration, Light Table has Instarepl, which is a good basic REPL.

We need Homebrew. It’s really easy to set up.

$ ruby -e "$(curl -fsSL https://raw.github.com/mxcl/homebrew/go)"

Next, use brew to install leiningen, which is almost like the rails script for ruby, except probably even more comprehensive.

$ brew install leiningen

For reference, a summary of the version numbers of my software is listed here.

Mac OS X 10.8.5
Homebrew 0.9.4
Leiningen 2.3.2
Clojure 1.5.1 (included with the latest version of Leiningen)

Create Project

$ lein new compojure myblog

Compojure does the routing (parsing of the path in your URL). Verify that the server works by running the following:

$ lein ring server

Go to http://localhost:3000 to make sure it works.

The root of the source tree (or the myblog folder) contains a file called project.clj. This is where you put dependency information. We’ll be adding things here as we build up the project.

The other thing to pay attention to right now is the myblog/src/myblog folder, which contains the Clojure source files for your project. There should just be one file at this point: handler.clj, which contains your routes.

Development

Getting a basic page up and running

If you look at handler.clj, you’ll find a route to the root of your site, already made for you:

(GET "/" [] "Hello World")

Changing “Hello World” to whatever simple string you want is trivial. But we want actual HTML, so we’ll use Hiccup to generate our HTML from Clojure data structures. I don’t want to clutter up handler.clj, so I want to create a new file.

Every file has its own namespace, so if you go the route of having separate files, you have to declare a namespace for each of them. So let’s create a new file called views.clj and fill it with code that will display an HTML page.

myblog/src/myblog/views.clj

(ns myblog.views
  (:require [hiccup.core :refer (html)]))
(defn layout [title & content]
  (html
    [:head [:title title]]
    [:body content]))
(defn main-page []
  (layout "My Blog"
    [:h1 "My Blog"]
    [:p "Welcome to my page"]))

As you can see, we have to “require” hiccup.core to use the html function, which is the only function we’re importing. It converts a nested vector to HTML tags.

We created a helper function called layout that takes care of setting up the page framework (if you’re familiar with Ruby on Rails, this is analogous to application.html.erb). The ampersand means content will collect any arguments beyond the first one (which is the title) into a Clojure list.

We then created a function called main-page that will be called by our routes handler. When using defn, the empty brackets after the function name are required.

Before we can call this function from our handler, we have to add [myblog.views :as views] to our list of requirements to import all the functions in views.clj and to make an alias that makes our referencing easier. With :as, we don’t have to write myblog.views/function-name; we can just write views/function-name.

myblog/src/myblog/handler.clj

(ns myblog.handler
  (:use compojure.core)
  (:require [compojure.handler :as handler]
            [compojure.route :as route]
            [myblog.views :as views]))
...

Now we replace "Hello World" with (views/main-page), like this:

(GET "/" [] (views/main-page))

You can refresh your browser to see it in action.

Administrator Section

Now we need to create a protected part of the website for the administrator. In this section, we’ll organize the routes in preparation for our authentication system, but we won’t apply security just yet.

First, move your current routes (except for the not-found route) into a new route function called public-routes.

Then add a protected-routes function, with a route to "/admin". Let’s also call the function generating that view views/admin-blog-page, which we haven’t written yet.

Finally, we simplify app-routes. Your handler.clj should look like this:

myblog/src/myblog/handler.clj

(ns ...
(defroutes public-routes
  (GET "/" [] (views/main-page))
  (route/resources "/"))
(defroutes protected-routes
  (GET "/admin" [] (views/admin-blog-page)))
(defroutes app-routes
  public-routes
  protected-routes
  (route/not-found "Not Found"))
(def app ...

In views.clj, we have to do three things:

  1. Add the admin-blog-page function, which will generate the admin’s page for us. Fortunately, we’ve already written layout, which will help to keep our code compact.
  2. Write a helper post-summary that will display a summary of a post.
  3. Add myblog.posts to the requirements, even though we have not written it yet. The posts file will take care of interacting with the database.

myblog/src/myblog/views.clj

(ns myblog.views
  (:require [hiccup.core :refer (html)]
            [myblog.posts :as posts]))
(defn layout ...
(defn main-page ...
; Post is a map corresponding to a record from the database
(defn post-summary [post]
  (let [id (:id post)
        title (:title post)
        body (:body post)
        created_at (:created_at post)]
    [:section
      [:h3 title]
      [:h4 created_at]
      [:section body]
      [:section.actions
        [:a {:href (str "/admin/" id "/edit")} "Edit"] " / "
        [:a {:href (str "/admin/" id "/delete")} "Delete"]]]))
(defn admin-blog-page []
  (layout "My Blog - Administer Blog"
    [:h1 "Administer Blog"]
    [:h2 "All my posts"]
    [:a {:href "/admin/add"} "Add"]
    (map #(post-summary %) (posts/all))))    

We will define posts/all and create the /admin/add page in the next section.

CRUD

I assume you know how to set up a MySQL database on your computer. You can use phpMyAdmin, the command line, or if you’re using OS X, Sequel Pro. Create a database and a user and give that user access to the database you just created. For purposes of illustration, I will call the database myblog, and create a user/password pair (user1, pass1). Also create a table called posts, with the following fields and types:

id, INT
title, VARCHAR(255)
body, MEDIUMTEXT
created_at, DATETIME
updated_at, DATETIME

I would recommend creating a mock record in posts for the next section.

Before we build each part of CRUD one by one, let’s add some dependency information to project.clj so that we can talk to the MySQL database.

myblog/project.clj

...
  :dependencies [[ ... ]
                 [ ... ]
                 [org.clojure/java.jdbc "0.3.0-alpha5"]
                 [mysql/mysql-connector-java "5.1.25"]]
  ...

Let’s now create posts.clj. It will contain our posts model.

myblog/src/myblog/posts.clj

(ns myblog.posts
  (:refer-clojure :exclude [get])
  (:require [clojure.java.jdbc :as j]
            [clojure.java.jdbc.sql :as s]))
(def mysql-db {:subprotocol "mysql"
               :subname "//localhost:3306/myblog"
               :user "user1"
               :password "pass1"
               :zeroDateTimeBehavior "convertToNull"})

Notice I excluded the get function from the Clojure core, in preparation for having my own function called get in this namespace. My friend tells me that best practice is to use a prefix (so instead of calling my new function get, I should use sql-get), but this example shows you how to resolve function name conflicts in case you need to use it. I also included the two JDBC dependencies, and put my database connection information into a function with zeroDateTimeBehavior set to "convertToNull" so that “0000-00-00 00:00:00” will be returned by JDBC as NULL values.

Read

Now we add the all function that we are calling from views.clj, as well as a function for retrieving a specific post by its ID.

myblog/src/myblog/posts.clj

(ns ...
(def mysql-db ...
(defn all []
  (j/query mysql-db
    (s/select * :posts)))
(defn get [id]
  (first (j/query mysql-db
    (s/select * :posts (s/where {:id id})))))

At this point, the site is in a viewable state, where there are no undefined functions and all the dependencies have been met (however, not all the links go somewhere yet). Since we added dependencies since the last time the server was started, we need to interrupt the current instance of the server using control-C (C) and run lein ring server again.

If you made a mock post as suggested earlier, you can go to http://localhost:3000/admin and you will see your mock post there with two links “Edit” and “Delete.”

Create

To create a post, we need a function in the model that does the creating of a new record. While we’re doing that, we also need a symbol (defined by def instead of defn) to feed the MySQL database the current time. If you’re using defn, make sure to include the empty brackets after the function name.

myblog/src/myblog/posts.clj

(ns ...
(def mysql-db ...
(def now
  (str (java.sql.Timestamp. (System/currentTimeMillis))))
(defn all ...
(defn get ...
(defn create [params]
  (j/insert! mysql-db :posts (merge params {:created_at now :updated_at now})))

Here, params is hash that contains the data of the fields from whatever form we’re going to post from.

We also need a form for us to create a new post. So include [hiccup.form :as f] in the namespace for views.clj to be able to access some form helpers. Then create a new function in views.clj:

myblog/src/myblog/views.clj

(ns myblog.views
  (:require [hiccup.core :refer (html)]
            [hiccup.form :as f] ; <-- add this
            [myblog.posts :as posts]))
...
(defn add-post []
  (layout "My Blog - Add Post"
    (list
      [:h2 "Add Post"]
      (f/form-to [:post "/admin/create"]
        (f/label "title" "Title")
        (f/text-field "title") [:br]
        (f/label "body" "Body") [:br]
        (f/text-area {:rows 20} "body") [:br]
        (f/submit-button "Save")))))

The form will use POST to issue a call to the route /admin/create. We now have to update our handler.clj with three things:

  1. create the /admin/create route,
  2. require myblog.posts since in our /admin/create route, we will call posts/create, and
  3. require ring.util.response to use its redirection function after calling posts/create.

myblog/src/myblog/handler.clj

(ns myblog.handler
  (:use compojure.core)
  (:require [compojure.handler :as handler]
            [compojure.route :as route]
            [myblog.views :as views]
            [myblog.posts :as posts]         ; <-- add this line
            [ring.util.response :as resp]))  ; <-- and this line
...
(defroutes protected-routes
  (GET "/admin" [] (views/admin-blog-page))
  (GET "/admin/add" [] (views/add-post))
  (POST "/admin/create" [& params]
    (do (posts/create params)
        (resp/redirect "/admin"))))

Notice I’m doing two things in the POST handler by using do: I first call the posts/create function to insert the record into the database, and then I redirect back to the main admin page.

Update

We add a function to our posts model:

myblog/src/myblog/posts.clj

...
(defn save [id params]
  (j/update! mysql-db :posts params (s/where {:id id})))

We make a page for the edit form:

myblog/src/myblog/views.clj

...
(defn edit-post [id]
  (layout "My Blog - Edit Post"
  (list
    (let [post (posts/get id)]
    [:h2 (str "Edit Post " id)]
    (f/form-to [:post "save"]
      (f/label "title" "Title")
      (f/text-field "title" (:title post)) [:br]
      (f/label "body" "Body") [:br]
      (f/text-area {:rows 20} "body" (:body post)) [:br]
      (f/submit-button "Save"))))))

We add two routes, one for the edit page, and one for the action to update the record (which we’ll call save). Your protected-routes should look like this now:

myblog/src/myblog/handler.clj

(defroutes protected-routes
  (GET "/admin" [] (views/admin-blog-page))
  (GET "/admin/add" [] (views/add-post))
  (POST "/admin/create" [& params]
    (do (posts/create params)
        (resp/redirect "/admin")))
  (GET "/admin/:id/edit" [id] (views/edit-post id))
  (POST "/admin/:id/save" [& params]
    (do (posts/save (:id params) params)
        (resp/redirect "/admin"))))

Delete

Delete is a lot easier to write than the others:

myblog/src/myblog/posts.clj

...
(defn delete [id]
  (j/delete! mysql-db :posts (s/where {:id id})))

Update the routes by adding this to the protected-routes function:

myblog/src/myblog/handler.clj

...
(GET "/admin/:id/delete" [id]
  (do (posts/delete id)
    (resp/redirect "/admin")))

What about REST?

To keep things simple, I made the delete action use GET and the save action use POST. Ideally, we would use DELETE and PUT, but that requires some more abstractions, and may be a topic of a future tutorial.

Authentication

Now that we have the basic functionality of the website down, let’s make sure only someone who is authorized can access the admin section.

Add [ring-basic-authentication "1.0.2"] to the dependency list in project.clj:

myblog/project.clj

(defproject myblog "0.1.0-SNAPSHOT"
  :description "FIXME: write description"
  :url "http://example.com/FIXME"
  :dependencies [[org.clojure/clojure "1.5.1"]
                 [compojure "1.1.5"]
                 [org.clojure/java.jdbc "0.3.0-alpha5"]
                 [mysql/mysql-connector-java "5.1.25"]
                 [ring-basic-authentication "1.0.2"]]   ; <-- add this line
  :plugins [[lein-ring "0.8.5"]]
  :ring {:handler myblog.handler/app}
  :profiles
  {:dev {:dependencies [[ring-mock "0.1.5"]]}})

Modify handler.clj as follows:

myblog/src/myblog/handler.clj

(ns myblog.handler
  (:require ...
            [ring.middleware.basic-authentication :refer :all]))
(defn authenticated? [name pass]
  (and (= name "user")
       (= pass "pass")))
...
(defroutes app-routes
  public-routes
  (wrap-basic-authentication protected-routes authenticated?)
  (route/not-found "Not Found"))

That’s it!

Full Code Listing

myblog/project.clj

(defproject myblog "0.1.0-SNAPSHOT"
  :description "FIXME: write description"
  :url "http://example.com/FIXME"
  :dependencies [[org.clojure/clojure "1.5.1"]
                 [compojure "1.1.5"]
                 [org.clojure/java.jdbc "0.3.0-alpha5"]
                 [mysql/mysql-connector-java "5.1.25"]
                 [ring-basic-authentication "1.0.2"]]
  :plugins [[lein-ring "0.8.5"]]
  :ring {:handler myblog.handler/app}
  :profiles
  {:dev {:dependencies [[ring-mock "0.1.5"]]}})

myblog/src/myblog/handler.clj

(ns myblog.handler
  (:use compojure.core)
  (:require [compojure.handler :as handler]
            [compojure.route :as route]
            [myblog.views :as views]
            [myblog.posts :as posts]
            [ring.util.response :as resp]
            [ring.middleware.basic-authentication :refer :all]))
(defn authenticated? [name pass]
  (and (= name "user")
       (= pass "pass")))
(defroutes public-routes
  (GET "/" [] (views/main-page))
  (route/resources "/"))
(defroutes protected-routes
  (GET "/admin" [] (views/admin-blog-page))
  (GET "/admin/add" [] (views/add-post))
  (POST "/admin/create" [& params]
    (do (posts/create params)
        (resp/redirect "/admin")))
  (GET "/admin/:id/edit" [id] (views/edit-post id))
  (POST "/admin/:id/save" [& params]
    (do (posts/save (:id params) params)
        (resp/redirect "/admin")))
  (GET "/admin/:id/delete" [id]
    (do (posts/delete id)
        (resp/redirect "/admin"))))
(defroutes app-routes
  public-routes
  (wrap-basic-authentication protected-routes authenticated?)
  (route/not-found "Not Found"))
(def app
  (handler/site app-routes))

myblog/src/myblog/views.clj

(ns myblog.views
  (:require [hiccup.core :refer (html)]
            [hiccup.form :as f]
            [myblog.posts :as posts]))
(defn layout [title & content]
  (html
    [:head [:title title]]
    [:body content]))
(defn main-page []
  (layout "My Blog"
    [:h1 "My Blog"]
    [:p "Welcome to my page"]))
; Post is a map corresponding to a record from the database
(defn post-summary [post]
  (let [id (:id post)
        title (:title post)
        body (:body post)
        created_at (:created_at post)]
    [:section
      [:h3 title]
      [:h4 created_at]
      [:section body]
      [:section.actions
        [:a {:href (str "/admin/" id "/edit")} "Edit"] " / "
        [:a {:href (str "/admin/" id "/delete")} "Delete"]]]))
(defn admin-blog-page []
  (layout "My Blog - Administer Blog"
    [:h1 "Administer Blog"]
    [:h2 "All my posts"]
    [:a {:href "/admin/add"} "Add"]
    (map #(post-summary %) (posts/all))))
(defn add-post []
  (layout "My Blog - Add Post"
    (list
      [:h2 "Add Post"]
      (f/form-to [:post "/admin/create"]
        (f/label "title" "Title")
        (f/text-field "title") [:br]
        (f/label "body" "Body") [:br]
        (f/text-area {:rows 20} "body") [:br]
        (f/submit-button "Save")))))
(defn edit-post [id]
  (layout "My Blog - Edit Post"
  (list
    (let [post (posts/get id)]
    [:h2 (str "Edit Post " id)]
    (f/form-to [:post "save"]
      (f/label "title" "Title")
      (f/text-field "title" (:title post)) [:br]
      (f/label "body" "Body") [:br]
      (f/text-area {:rows 20} "body" (:body post)) [:br]
      (f/submit-button "Save"))))))

myblog/src/myblog/posts.clj

(ns myblog.posts
  (:refer-clojure :exclude [get])
  (:require [clojure.java.jdbc :as j]
            [clojure.java.jdbc.sql :as s]))
(def mysql-db {:subprotocol "mysql"
               :subname "//localhost:3306/myblog"
               :user "jluo"
               :password "podunkoregon"
               :zeroDateTimeBehavior "convertToNull"})
(defn all []
  (j/query mysql-db
    (s/select * :posts)))
(defn get [id]
  (first (j/query mysql-db
    (s/select * :posts (s/where {:id id})))))
(def now
  (str (java.sql.Timestamp. (System/currentTimeMillis))))
(defn create [params]
  (j/insert! mysql-db :posts (merge params {:created_at now :updated_at now})))
(defn save [id params]
  (j/update! mysql-db :posts params (s/where {:id id})))
(defn delete [id]
  (j/delete! mysql-db :posts (s/where {:id id})))

Deployment

There are a myriad of ways to set up your Clojure deployment. The way I’m presenting is not the best, but it’s transparent, and it allows you to host multiple sites on port 80 running on different languages (the real reason I’m using Apache).

Run in your project directory, run

$ lein ring uberwar

The .war file will be located in your myblog/target directory.

Server Setup

I assume you are running Ubuntu 12.04 and you have Apache installed. I also assume you have a JVM installed.

We need to install Tomcat.

$ sudo apt-get install tomcat7-user

We’re going to use a private instance of Tomcat.

Create a private instance directory. I created mine in my home directory.

$ tomcat7-instance-create my-tomcat-instance

Now edit server.xml in ~/my-tomcat-instance/conf and uncomment the following line:

<Connector port="8009" protocol="AJP/1.3" redirectPort="8443" />

Make sure mod_jk is installed

$ sudo apt-get install libapache2-mod-jk

Now create a workers.properties file for Apache:

$ sudo nano /etc/apache2/workers.properties

Add the following to the file:

# Define 1 real worker using ajp13 
worker.list=worker1 
# Set properties for worker (ajp13) 
worker.worker1.type=ajp13 
worker.worker1.host=localhost
worker.worker1.port=8009

Make sure mod_jk is enabled.

$ sudo a2enmod jk

In your Apache virtual host settings (inside /etc/apache2/sites-available/), add (or edit):

<VirtualHost *:80>
        ServerAdmin admin@domain.com
        ServerName test.domain.com
        ServerAlias test.domain.com
        JkMount /* worker1
</VirtualHost>

Uploading

Now upload the .war file in myblog/target on your development machine to the server, and place it in ~/my-tomcat-instance/webapps. Finally, rename it to ROOT.war.

To start up your Tomcat instance,

$ my-instance/bin/startup.sh

Tomcat should automatically detect your .war file and launch the application.

Make sure you have your production database running with the user and database information specified in your posts.clj file, or else you’ll get an internal server error when you try to access the admin part of the site!

If you don’t plan to use the root of the subdomain to run your app, then replace JkMount /* worker1 in the virtual host settings with JkMount /myblog* worker1, and upload your file as myblog.war instead of ROOT.war.

Conclusion

Clojure is a maturing language. Therefore, documentation at this point is kind of all over the place. It took several days to figure out the basics. Most of the time was spent on logistical things and non-obvious “gotchas” that prevented me from really enjoying the language. Because the Clojure ecosystem is still changing, this tutorial will undoubtedly be outdated within a few years, but I do hope it helps someone in the meantime. E-mail me with comments/corrections/constructive criticisms.

References


Thanks to Wei for proofreading this tutorial!