Introduction to the Compojure web framework

252 views
Skip to first unread message

James Reeves

unread,
Jul 13, 2008, 7:02:40 PM7/13/08
to Clojure
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.

--
James Reeves

peg

unread,
Jul 14, 2008, 5:33:57 AM7/14/08
to Clojure
Hi,

A simple question: I've seen list approach -- '(:tag {attrs} content )
-- and vector approach -- [:tag ... ] -- for html representation,
which do you think is better ... easier to use or program, memory
use, velocity ?

Thanks,
Phil

James Reeves

unread,
Jul 14, 2008, 8:19:50 AM7/14/08
to Clojure
Personally, I don't think there's a lot between them. I prefer the
vector approach because it looks a little cleaner, in my opinion. I
can't imagine there'd be much difference in performance or memory use
if all you're doing is representing a HTML DOM.

For the sake of comparison:

(html
`(:body
(:h1 ~title)
(:ul
~@(domap item @items
`(:li ~(str item)))))

(html
[:body
[:h1 title]
[:ul
(domap item @items
[:li (str item)])]])

The latter looks a little better to me. Others may disagree :)

--
James

Chouser

unread,
Jul 14, 2008, 10:09:22 AM7/14/08
to clo...@googlegroups.com

The most important difference to me is the vector syntax evaluates to
a vector, while the list syntax evaluates to function calls. In other
words, [:img {:src "foo.jpg"}] needs no other definitions to stand for
an img element. It can be created in any context, passed around, and
eventually evaluated by a function or macro to turn it into HTML.

On the other hand (:img {:src "foo.jpg"}) in a normal evaluation
context means "look up the :img key in the {:src "foo.jpg"} hash".
This is a valid interpretation, a correct Clojure expression, but it
returns nil in this case. Probably not what you meant. So every time
you want to create an img element with this syntax you have to quote
it or put it in a macro. Quoting it like '(:img {:src "foo.jpg"})
means you can't have any computation going on inside the expression to
generate content: '(:img {:src (jpg-url "foo")}) won't call my jpg-url
function. Using a macro like (html (:img {:src "foo.jpg"}) is wordy
and requires the macro to pick through the tree and figure out what
should be evaluated as a function and what should be left as a
literal.

I guess another option would be to define functions for every tag you
might want: (img {:src "foo.jpg"}) could produce what you want if
there was an img function. But this requires pre-generating all the
tag functions you'd want to use. ...and what would be the benefit
when we have the nice vector syntax built right in?

I guess my preference is clear. :-)
--Chouser

peg

unread,
Jul 14, 2008, 12:54:01 PM7/14/08
to Clojure
OK. That's understandable. But in Lisp, list should be The 'natural'
forma

peg

unread,
Jul 14, 2008, 12:58:13 PM7/14/08
to Clojure
Sorry. Error typing last message. Vector representation seems to be
more usable and a little cleaner (no quotes needed). But is Vector
manipulation as integrated as list in Lisp or Clojure ?

Paul Barry

unread,
Jul 14, 2008, 2:29:52 PM7/14/08
to clo...@googlegroups.com
Compojure looks really cool, hopefully I will find some time to play with.  One question I have is with the you create the resources.  Do you have a way of doing regexp based routing?  Like GET /bookmarks/1 goes to the show action of the bookmarks resource with the id param set to 1, or something more complicated like GET /2008/07/04/happy-4th, and have that map to an action that passes the params year=2008, month=07, date=04, title=happy-4th?

I've been thinking about how to do routing in Clojure and I would love to do something that works like mutlimethod, but I can't think of a way to do it because it requires the use of regexps.  I think what Rails and Merb do for routing is just build a list of regexps and then test the incoming request against each regexp until it finds a match.  (I could be totally wrong, I haven't looked at how that is implemented)  I was going to do that as a first pass in Clojure, but I'm wondering if there is a more elegant, efficient way.

For those not familiar with Rails or Merb, routing is the process of taking an incoming HTTP request and dispatching it to a specific method, based on a number of parameters.  You set up the routes in a DSL that looks something like:

map.connect "/comments/:id", :controller => 'comments', :action => 'show'

Which would match for a url like /comments/1, so it would call the show action of the comments controller and pass a parameter of id=1, along with any other query string parameters.  But they can get complicated, routing on things like the HTTP method, or if a specific parameter matches a certain regexp.

James Reeves

unread,
Jul 14, 2008, 3:30:52 PM7/14/08
to Clojure
On Jul 14, 7:29 pm, "Paul Barry" <pauljbar...@gmail.com> wrote:
> Compojure looks really cool, hopefully I will find some time to play with.
>  One question I have is with the you create the resources.  Do you have a
> way of doing regexp based routing?  Like GET /bookmarks/1 goes to the show
> action of the bookmarks resource with the id param set to 1, or something
> more complicated like GET /2008/07/04/happy-4th, and have that map to an
> action that passes the params year=2008, month=07, date=04, title=happy-4th?

Yep, in fact it uses exactly the same 'routes' syntax as Rails, Merb,
Sinatra and all the other Ruby frameworks:

(GET "/demo/:controller/:action/:id"
(html
[:p "The controller is: " (route :controller)]
[:p "The action is: " (route :action)]
[:p "The id is: " (route :id)]))

I've elected to keep the HTTP parameters separate from the keywords in
the route. So parameters are accessed via 'param', and the named
pieces of the route are accessed through 'route'.

The implementation is somewhat incomplete though, since I haven't
implemented wildcards yet. In future, you'll be able to do things like
this:

(GET "/foo/*"
(str "The path after /foo = " (route :*)))

And I'll also add in support for raw regexs:

(GET #"/messages/(\d+)"
(str "The regex match no. 1 = " (route 1)))

--
James

Chouser

unread,
Jul 14, 2008, 3:44:11 PM7/14/08
to clo...@googlegroups.com
On Mon, Jul 14, 2008 at 3:30 PM, James Reeves
<weave...@googlemail.com> wrote:
>
> Yep, in fact it uses exactly the same 'routes' syntax as Rails, Merb,
> Sinatra and all the other Ruby frameworks:
>
> (GET "/demo/:controller/:action/:id"
> (html
> [:p "The controller is: " (route :controller)]
> [:p "The action is: " (route :action)]
> [:p "The id is: " (route :id)]))

I'm curious if you're amenable to splitting up Compojure into smaller
pieces. It seems to me there's nothing inherently connecting the
routing features with the templating piece. There may be other
sensible divisions as well. My thinking is that by having smaller
pieces, people could come up with customized or innovative pieces in
place of some of Compojure without confronting an all-or-nothing type
decision.

What do you think?
--Chouser

James Reeves

unread,
Jul 14, 2008, 6:16:52 PM7/14/08
to Clojure
> I'm curious if you're amenable to splitting up Compojure into smaller
> pieces.  It seems to me there's nothing inherently connecting the
> routing features with the templating piece.  There may be other
> sensible divisions as well.  My thinking is that by having smaller
> pieces, people could come up with customized or innovative pieces in
> place of some of Compojure without confronting an all-or-nothing type
> decision.
>
> What do you think?
> --Chouser

To an extent, this is the case already. Compojure is divided into
modules that are designed to be independent of one another. You could,
for instance, delete the html module from your source tree and
Compojure would still run fine.

The modules do rely on functions defined in the various libraries in
the lib directory, so they're not completely standalone. I'm not quite
sure how far I should go in separating them out. In theory, the http
and the core of the html modules could be spun off as standalone
library files for people to use - it certainly wouldn't take very long
to do this.

On the other hand, tying them to the framework enables them to share
common functions, and to have a more "batteries included" environment
to develop in. Unlike libraries contained in single .clj files, a
module directory can contain jars, documentation, static files like
images and stylesheets.

I'm unsure how best to divide Compojure up, whether to have it as a
loosely integrated framework, or as a collection of standalone library
files.

- James

Allen Rohner

unread,
Jul 14, 2008, 7:13:50 PM7/14/08
to Clojure
> On the other hand, tying them to the framework enables them to share
> common functions, and to have a more "batteries included" environment
> to develop in. Unlike libraries contained in single .clj files, a
> module directory can contain jars, documentation, static files like
> images and stylesheets.
>
> I'm unsure how best to divide Compojure up, whether to have it as a
> loosely integrated framework, or as a collection of standalone library
> files.
>

I've started playing with the framework, and I like it. The one issue
I have though, is where to put my source. my app code is checked
into ./app in my local branch of compojure, which seems like the wrong
approach, but I don't have a better solution because compojure wants
to load everything in ./app.

I was looking at translating ./script/repl into a clj file, just so I
could require that file and keep my code out of compojure.

Any better ideas?

Allen

James Reeves

unread,
Jul 15, 2008, 2:30:07 AM7/15/08
to Clojure
On Jul 15, 12:13 am, Allen Rohner <aroh...@gmail.com> wrote:
> I've started playing with the framework, and I like it. The one issue
> I have though, is where to put my source. my app code is checked
> into ./app in my local branch of compojure, which seems like the wrong
> approach, but I don't have a better solution because compojure wants
> to load everything in ./app.

This behaviour is the approach that Ruby frameworks like Ruby on Rails
take, with a lot of the libraries sitting under the same root as the
application code. But this behaviour in Compojure can be changed by
modifying config/boot.clj.

I'm beginning to think that maybe I should structure Compojure more
like a set of libraries, rather than as a framework that prefers a
fixed directory structure. I suspect I'll get rid of the "modules"
directory and move all the code into "lib/contrib" and "lib/
compojure". That way the libraries can be more easily detached from
the application code.

--
James

Allen Rohner

unread,
Jul 15, 2008, 10:17:51 AM7/15/08
to Clojure
> > I've started playing with the framework, and I like it. The one issue
> > I have though, is where to put my source. my app code is checked
> > into ./app in my local branch of compojure, which seems like the wrong
> > approach, but I don't have a better solution because compojure wants
> > to load everything in ./app.
>
> This behaviour is the approach that Ruby frameworks like Ruby on Rails
> take, with a lot of the libraries sitting under the same root as the
> application code. But this behaviour in Compojure can be changed by
> modifying config/boot.clj.

The ruby approach is not a bad one. It does take several decisions
away from the user when they are first getting started, which makes it
slightly easier to get started with rails. The difference is (as I'm
sure you know), rails has a program called rails which creates a
skeleton directory structure for your project. The down side of that
decision is there is a lot more overhead in terms of the code to
generate your directory structure and packaging rails in a .deb
or .rpm is a pain.

Chouser

unread,
Jul 15, 2008, 10:58:28 AM7/15/08
to clo...@googlegroups.com
On Tue, Jul 15, 2008 at 10:17 AM, Allen Rohner <aro...@gmail.com> wrote:
>
> The ruby approach is not a bad one. It does take several decisions
> away from the user when they are first getting started, which makes it
> slightly easier to get started with rails. The difference is (as I'm
> sure you know), rails has a program called rails which creates a
> skeleton directory structure for your project. The down side of that
> decision is there is a lot more overhead in terms of the code to
> generate your directory structure and packaging rails in a .deb
> or .rpm is a pain.

I was a bit suspicious of the code-generation tools in Rails, but they
seemed to work well enough. ...until it came time to upgrade. Having
recently gone through a Rails upgrade on a web app, I am now firmly
against this approach. Mixing libraries and generated code into the
same directory tree as the application source just seems like a
guaranteed mess in the long run.

Perhaps having a fixed directory tree, and having tools to generate
it, isn't that bad -- a nice way to get started. Even generating
minimal stubs that the developer is then expected to fill out and/or
replace would be okay. But some of the Rails generators go quite a
bit further than that and have caused me pain.

--Chouser

James Reeves

unread,
Jul 15, 2008, 3:43:11 PM7/15/08
to Clojure
I've just finished decoupling the Compojure code from the framework
structure. Compojure is now less a framework, and more a set of
libraries with some optional shell scripts and jar files included for
convinience. All of the libraries for Compojure are in "lib", so if
you don't want to use the default directory structure, the included
jars or the REPL shell script, you can just grab the "lib" directory
and integrate it into your own application.

I've also added script/run, for non-interactive use. Both script/repl
and script/run start by running config/boot.clj. By default, boot.clj
loads in all the Compojure libraries, and then starts a Jetty HTTP
server. It's only about a dozen lines of Clojure code, so it should be
easy to customize.

- James

graham

unread,
Jul 20, 2008, 7:55:29 AM7/20/08
to Clojure
Hi,

I'm really enjoying playing with this framework. What's the right way
to trigger PUT and DELETE routes? Is there a way to fake it with
hidden fields like Sinatra/Rails?

Graham

Moxley Stratton

unread,
Jul 21, 2008, 4:50:50 PM7/21/08
to Clojure
Hi James,

I've been giving Compojure some heavy use these last couple days. It's
a nice framework, and lets me work with within a context I know well
(web development) for learning Clojure. I have some comments about it
and a small contribution. It seems more appropriate to me to post
these on a dedicated discussion list or a Wiki. Would you consider
opening up a mailing list or wiki for Compojure?

Thanks

Moxley

Matt Revelle

unread,
Jul 21, 2008, 5:15:07 PM7/21/08
to clo...@googlegroups.com
A Compojure mailing list is a good idea. Would like to start bouncing
ideas on how to include optional use of delimited continuations for
stateful operations in harmony with REST.

On Jul 21, 2008, at 16:50, Moxley Stratton <moxley....@gmail.com>
wrote:

James Reeves

unread,
Jul 22, 2008, 7:02:44 PM7/22/08
to Clojure
There currently isn't a way to do this, besides using XmlHttpRequest.
There should be a way, and it might as well be to add a hidden
"_method" field, the same as Sinatra/Rails. I'll make the appropriate
adjustments to the http library by this weekend, unless someone wants
to beat me to it :)

On Jul 21, 10:15 pm, Matt Revelle <mreve...@gmail.com> wrote:
> A Compojure mailing list is a good idea. Would like to start bouncing
> ideas on how to include optional use of delimited continuations for
> stateful operations in harmony with REST.
>
> On Jul 21, 2008, at 16:50, Moxley Stratton <moxley.strat...@gmail.com>
> wrote:
>
> > Hi James,
>
> > I've been giving Compojure some heavy use these last couple days. It's
> > a nice framework, and lets me work with within a context I know well
> > (web development) for learning Clojure. I have some comments about it
> > and a small contribution. It seems more appropriate to me to post
> > these on a dedicated discussion list or a Wiki. Would you consider
> > opening up a mailing list or wiki for Compojure?

Sure, if there are people interested, a separate discussion group
might be appropriate. I've set up a Compojure Google group at:
http://groups.google.com/group/compojure

--
James

graham

unread,
Jul 23, 2008, 11:02:19 AM7/23/08
to Clojure
Thanks for the reply James. I'd have a go at the change but I don't
think my Clojure is quite up to it yet! Will the form-to method
automatically add the hidden field?

James Reeves

unread,
Jul 23, 2008, 2:41:12 PM7/23/08
to Clojure
On Jul 23, 4:02 pm, graham <gps...@googlemail.com> wrote:
> Thanks for the reply James.  I'd have a go at the change but I don't
> think my Clojure is quite up to it yet!  Will the form-to method
> automatically add the hidden field?

Once I implement it, yep.

--
James
Reply all
Reply to author
Forward
0 new messages