A simple web application in Clojure using ring and enlive

By: on February 27, 2010

This post walks through the steps for using Clojure to write an extremely simple web application. The web application will use the Ring and Enlive libraries.

Firstly install Leiningen, this is a simple build tool for Clojure, follow the instructions on the web page and place the lein somewhere on your executable path.

Now use leiningen to create a new clojure project; I always name my projects after vegetables so we will call this one sprout. Execute this command at the command line:

% lein new sprout

This will create a directory called sprout. Change into the newly created directory and issue the following commands:

sprout% lein deps
sprout% lein test

The first command will download some dependent libraries and the second command will run a default unit test that should fail like this:

     [null] Testing sprout.core-test
     [null] FAIL in (replace-me) (core_test.clj:6)
     [null] expected: false
     [null]   actual: false
     [null] Ran 1 tests containing 1 assertions.
     [null] 1 failures, 0 errors.
     [null] --------------------
     [null] Total:
     [null] Ran 1 tests containing 1 assertions.
     [null] 1 failures, 0 errors.

If this all goes successfully we have a project ready to work with. Working with Clojure is made a lot easier if you have a working REPL to interact with, create a file called clj in the project directory containing a script like this:

#!/bin/bash
    LIBS=`printf "%s:" lib/*.jar | sed s/:$//`
    if [ $# -eq 0 ]; then
         java -cp $LIBS:src clojure.main
    else
         java -cp $LIBS:src clojure.main $1 -- $@
    fi

Make sure this script is executable and check that it works from the command line like this:

sprout%./clj
Clojure 1.1.0
user=> (+ 2 3)
5
user=> ^C
sprout%

Now edit the project.clj file to add dependencies for the Ring and Enlive libraries, the file should look like this:

(defproject sprout "1.0.0-SNAPSHOT"
  :description "Simple program to play with ring and enlive"
  :dependencies [[org.clojure/clojure "1.1.0"]
                 [org.clojure/clojure-contrib "1.1.0"]
                 [ring "0.1.1-SNAPSHOT"]
                 [enlive "1.0.0-SNAPSHOT"]])

Now use leiningen to download the dependencies:

sprout% lein deps

Now let us create a basic application that will serve some web pages, edit the file src/sprout/core.clj to look like this:

(ns sprout.core)

(defn router [req]
  (condp = (:uri req)
    "/error"
      {:status 404
       :headers {"Content-Type" "text/html"}
       :body    (str "Error")}
    "/info"
      {:status 200
       :headers {"Content-Type" "text/html"}
       :body    (str "This is Ring on " (:server-name req))
      }
    {:status 200
     :headers {"Content-Type" "text/html"}
     :body    (str "Welcome")}))

(defn app [req]
  (router req))

This contains two functions, the router function generates different pages dependent on the requested uri and the app function that forwards a request to the router. We can test this code with a unit test, edit the file test/sprout/core_test.clj and make it look like this:

(ns sprout.core-test
  (:use [sprout.core] :reload-all)
  (:use [clojure.test]))

(deftest not-found-error
  (let [req {:uri "/error"}
        resp (app req)]
    (is (= 404 (:status resp)))))

(deftest default-page
  (let [req {:uri "/index.html"}
        resp (app req)]
    (is (= 200 (:status resp)))))

(deftest info-page
  (let [req {:uri "/info"
             :server-name "test"}
        resp (app req)]
    (testing "Info page shown"
      (is (= 200 (:status resp)))
      (is (= "This is Ring on test" (:body resp))))))

You can run this test with lein test at the command line.

So far this is pure functional Clojure with no real web pages in site, just a map being passed into a function and a different map being returned, the maps happen to be keyed with keys that look a bit like http requests and responses. The Ring library wraps these functions with adapters that talk to real web containers we will use Jetty to demonstrate real web pages.

Create a new file in src/sprout called run.clj and edit it to appear like this:

(ns sprout.run
  (use [ring.adapter.jetty])
  (use [sprout.core]))

(run-jetty app {:port 8080})

Now from the command line do this:

sprout%./clj src/sprout/run.clj
2010-02-27 14:08:03.602::INFO:  Logging to STDERR via org.mortbay.log.StdErrLog
2010-02-27 14:08:03.604::INFO:  jetty-6.1.14
2010-02-27 14:08:03.727::INFO:  Started SocketConnector@0.0.0.0:8080

Using a browser visit the urls http://localhost:8080, http://localhost:8080/info and http://localhost:8080/error. All being well you should see a welcome page, an info page and a 404 error.

Ring provides a further level of wrapper functions that enable html processing pipelines to be built using simple functions written in Clojure. The pages you have seen so far haven’t looked very impressive, they are not even valid HTML! We could embed the HTML in the code or write a library to generate HTML in Clojure but instead we will use Enlive.

Enlive is a selector based templating library, this differs from templating languages such as JSP or ASP in that the raw html remains untouched, functions are written that use css selectors to replace and augment the existing html. Create a file in the src/sprout directory called enlive.clj and edit it to appear like this:

(ns sprout.enlive
  (:use [net.cgrand.enlive-html]))

(deftemplate t1 "templates/t1.html" []
  [:h1]   (content "heading"))

(defn enlive-template []
  (apply str(t1)))

The deftemplate declaration creates a template called t1, that takes no parameters and substitutes the content of any h1 tags with the word heading. The function enlive-template just applies the template to generate HTML.

Now add a new mapping to the router defined in src/sprout/core.clj to use this function, the file should now look like this:

(ns sprout.core
  (:use sprout.enlive))

(defn router [req]
  (condp = (:uri req)
    "/error"
      {:status 404
       :headers {"Content-Type" "text/html"}
       :body    (str "Error")}
    "/info"
      {:status 200
       :headers {"Content-Type" "text/html"}
       :body    (str "This is Ring on " (:server-name req))
      }
    "/enlive"
      {:status 200
       :headers {"Content-Type" "text/html"}
       :body (enlive-template)
      }
    {:status 200
     :headers {"Content-Type" "text/html"}
     :body    (str "Welcome")}))

(defn app [req]
  (router req))

Running the run script again:

prout%./clj src/sprout/run.clj
2010-02-27 15:10:35.476::INFO:  Logging to STDERR via org.mortbay.log.StdErrLog
2010-02-27 15:10:35.479::INFO:  jetty-6.1.14
2010-02-27 15:10:35.528::INFO:  Started SocketConnector@0.0.0.0:8080

Now go to http://localhost:8080/enlive and an HTML page should be visible.

This has been an extremely basic introduction to using Ring and Enlive with Clojure to serve up web pages. I’ll be expanding on this example at a later date to dive deeper into the functionality of Ring, Enlive and Clojure.

FacebookTwitterGoogle+

4 Comments

  1. Alex Miller says:

    I’d be really interested in hearing what Compojure would (or would not) give you since it also uses Ring and can use Enlive.

  2. Stanton Teters says:

    Good article. One thing to point out though is that Leiningen lets you run a REPL without having to create the script using “lein repl”.

  3. Raju says:

    Nice article. Thank you.

    I just wanted to add to @Stanton’s comment – Per this

    http://github.com/technomancy/leiningen/tree/master/lein-swank/

    Add :dev-dependencies [leiningen/lein-swank "1.1.0"] to your project.clj, then a lein swank will start a repl server that you can connect with using emacs slime. That is, assuming you are using emacs.

    Regards,

  4. fbox says:

    Thanks for the introduction to Ring/Enlive. Very helpful.

    Initially I got a NullPointerException when I ran the final version of your code.

    It was because I hadn’t created a file templates/t1.html
    [File not found: templates/t1.html would have been a better exception :) I know it's not your code...]

    You don’t mention creating that file in your article AFAICS.

    I created a file like this:

    T1 Template

    Title Should Go Here

    [I had to add . to the class path in your clj script to let enlive find it. Like this (note the colon-dot at the end):

    LIBS=printf "%s:" lib/*.jar | sed s/:$//:.

    ]

Post a comment

Your email address will not be published.

*

You may use these HTML tags and attributes: <a href="" title=""> <abbr title=""> <acronym title=""> <b> <blockquote cite=""> <cite> <code> <del datetime=""> <em> <i> <q cite=""> <strike> <strong>