public
Description: Concise web framework for Clojure
Home | Edit | New

Tutorial

There have been a couple of posts in the group about Compojure, a web
framework that I’ve been working on. It’s still incomplete, but it’s
reached the stage where it’s starting to gain some stability. Some
people might be interested in it, so I’ve thrown together this post
with some information about the latest revision.

The language I work with from day to day is Ruby, so Compojure has a
lot in common with lightweight Ruby frameworks like Sinatra, and less
with existing Java frameworks. It’s designed for impatient, lazy
people like myself, so it’s quick to install and quick to start
developing in.

Compojure differs from similar Ruby frameworks in that it attempts to
be as functional as possible. Nor does it have the tight coupling to
SQL databases that, for instance, Ruby on Rails does. Other than that,
if you’ve ever programmed in Sinatra with Haml templates, Compojure
isn’t a million miles away from this.

Information on how to install Compojure, along with some documentation
on the various modules, is available at the GitHub repository:
http://github.com/weavejester/compojure/tree/master

A small tutorial follows on how one might create an application in
Compojure. It’s a very simple bookmark manager, but it covers enough
of the basics to be informative. I’ve uploaded the finished file:
http://clojure.googlegroups.com/web/bookmark-tut.clj

In the first two lines of the bookmarks app, we define the Clojure
data structures we use to store a list of bookmarks:
(defstruct bookmark :url :title)
(def bookmarks (ref []))

Preferably, these bookmarks should be persistent, so we don’t lose all
our data if we shut down the server. I’ve recently created a small
persistence library, so we’ll use that to load up any stored data
“bookmarks.store”:

(use persist) (restore “bookmarks.store”)

We need a way of displaying these bookmarks, so lets go ahead and
create a function that will wrap our HTML in a standard layout:
(defn html-doc
[title & body]
(html
(doctype “xhtml/transitional”)
[:html
[:head
[:title title]]
[:body
body]]))

A lot of people have recently created HTML generators for Clojure, and
this is my version. It uses vectors to create the HTML elements, and
attempts to format the output in as readable a fashion as possible. It
used to have a lot more “magic” and use lists instead, but I decided
to keep simple.

I also have a few helper functions like ‘doctype’ there, which will
spit out a XHTML/Transitional doctype declaration in all it’s verbose
glory.

Now we have the layout, next we need to render a list of bookmarks:
(defn list-bookmarks []
(unordered-list
(domap bm @bookmarks
(link-to (bm :url) (bm :title)))))

Okay, that uses quite a lot of custom functions, but they’re all
pretty simple. Expanded out, it becomes:

(defn list-bookmarks []
[:ul (map
(fn [bm]
[:li [:a {:href (bm :url)} (bm :title)]])
@bookmarks)])

The unordered-list function creates an unordered list from a sequence.
The domap function is a lot like doseq, but using the map function.
And link-to creates a HTML link. These helpers can be found in:
compojure/modules/html/helpers.clj

Viewing bookmarks is good, but we also want a way of adding to them.
This means we need a form, and there are a few helper functions to
help with that, too:

(defn bookmark-form [] (form-to [POST “/bookmarks”] (label :url “URL:”) (text-field :url) “\n” (label :title “Title:”) (text-field :title) “\n” (submit-tag “Post”)))

Most of that should be pretty self explanatory, especially if you’ve
used Ruby on Rails or anything like it before. The only unusual bit is
the form-to macro, which uses [POST “/bookmarks”] to indicate that the
form method should be “POST”, and the action should be “/bookmarks”.
This syntax is used because it mirrors the way you create HTML
resources in Compojure:

(GET “/bookmarks” (html-doc “Bookmarks” (list-bookmarks) (bookmark-form)))

That tells Compojure to respond to any HTTP GET requests to the “/
bookmarks” path. It renders the list of bookmarks, and the new
bookmark form. Pretty simple.

A little less simple is the corresponding POST resource:

(POST “/bookmarks” (dosave “bookmarks.store” [new-bookmark (struct bookmark (param :url) (param :title))] (commute bookmarks conj new-bookmark)) (redirect-to “/bookmarks”))

The dosave macro is a wrapper around dosync that appends the code of
the transation to the specified file. This allows the transactions to
be ‘replayed’ by the restore function. This is a very lightweight and
somewhat experimental persistence layer, but it seems to work out
pretty well. The syntax of the dosave function is:

(dosave filename bindings & body)

The bindings act like a let form, and tell the dosave macro which
variables should be ‘captured’ when saved. If we didn’t have this, the
restore function wouldn’t know the value of ‘new-bookmark’ when it
loaded the file.

Last edited by fuentesjr, Fri Oct 02 11:54:29 -0700 2009
Home | Edit | New
Versions: