I am writing a Compojure application that will host my journal on EricLavigne.net. In a previous article, I showed how to setup Compojure to run on a Slicehost VPS. The next step is to setup a database to store the journal articles. In this article I will show how to setup a PostgreSQL database, store some example articles in that database, and display those articles in a Compojure application.
Setting up PostgreSQL
The first step is to setup PostgreSQL. This includes installing the postgresql package, starting the database server, and creating a database. The database administration commands used below are in /usr/lib/postgresql/8.3/bin.
apt-get install postgresql
sudo -u postgres initdb -D /var/lib/postgresql/data
sudo -u postgres mkdir /var/lib/postgresql/log
sudo -u postgres pg_ctl -D /var/lib/postgresql/data \
-l /var/lib/postgresql/log/postgres.log start
sudo -u postgres createdb -O postgres production
Adding articles to the database
We need some example articles for testing whether the application is working correctly.
First we login to PostgreSQL.
psql -U postgres production
Next, from the PostgreSQL prompt, we create a new table for storing articles.
create table article (
id serial primary key,
title text not null unique check (trim(title) ''),
description text not null default '',
body text not null default '',
created timestamp not null default now(),
updated timestamp not null default now(),
published timestamp null default null
);
Next, create two example articles.
insert into article (title, description, body) values
('Article 1', 'My first article',
'<p>Paragraph 1 in article 1</p><p>Another paragraph</p>'),
('Article 2', 'My second article',
'<p>This article also has a paragraph</p><p>And another</p>');
We’re finished with the PostgreSQL prompt, so we quit the prompt.
\q
Accessing PostgreSQL from Clojure
In order to access PostgreSQL from Clojure, we need a new library.
cd /root/install wget http://jdbc.postgresql.org/download/postgresql-8.3-604.jdbc4.jar
In order for that library to be accessible to our application, it needs to be added to the classpath in /root/site/run. The new library can be added as the second-to-last line as shown below.
java -cp \ /root/install/compojure/compojure.jar:\ /root/install/compojure/deps:\ /root/install/compojure/deps/clojure.jar:\ /root/install/compojure/deps/clojure-contrib.jar:\ /root/install/compojure/deps/jetty-6.1.14.jar:\ /root/install/compojure/deps/jetty-util-6.1.14.jar:\ /root/install/compojure/deps/servlet-api-2.5-6.1.14.jar:\ /root/install/postgresql-8.3-604.jdbc4.jar:\ . clojure.lang.Repl site.clj
Finally, the interesting part. I’ll show the full contents of the new /root/site/site.clj and explain the new code later.
; Indicates which libraries we need, and the
; shorter names by which they will be called
(ns site
(:require [compojure.http.servlet :as servlet])
(:require [compojure.http.routes :as routes])
(:require [compojure.http.helpers :as http-helpers])
(:require [compojure.html :as html])
(:require [compojure.html.page-helpers :as page-helpers])
(:require [compojure.server.jetty :as jetty])
(:require [clojure.contrib.sql :as sql]))
; Information about the PostgreSQL database
(def db {:classname "org.postgresql.Driver"
:subprotocol "postgresql"
:subname "production"
:user "postgres"})
; Performs a database query and returns the results as a list
(defn sql-query [query]
(sql/with-connection db
(sql/with-query-results res
; query is string that may contain "?"
; which are replaced with later elements
; in this array
[query]
(into [] res))))
; Fetches all articles
(defn articles []
(sql-query "select * from article"))
; Converts "My Article" to "my-article" for use in URL
(defn article-title-to-url-name [title]
(.replaceAll
(.toLowerCase title)
"[^a-z0-9]+" "-"))
; Converts map of article attributes to a URL
(defn article-url [article]
(str
"/articles/"
(article-title-to-url-name
(article :title))))
; Fetch an article with the given title
(defn article [title]
(first
(filter
(fn [art]
(=
(article-title-to-url-name
title)
(article-title-to-url-name
(art :title))))
(articles))))
; HTML page for an article
(defn render-article [article]
(html/html
[:head [:title (article :title)]]
[:body
[:h1 (article :title)]
[:p [:em (article :description)]]
(article :body)]))
; Search for article by title, return HTML or redirect
(defn view-article [title]
(try
(render-article (article title))
(catch Exception ex
(http-helpers/redirect-to "/articles/"))))
; HTML link to an article
(defn render-article-link [article]
(page-helpers/link-to
(article-url article)
(article :title)))
; HTML page that lists all articles
(defn view-article-list []
(html/html
[:head [:title "Articles"]]
[:body
[:dl (mapcat
(fn [article]
(list
[:dt (render-article-link
article)]
[:dd (article
:description)]))
(articles))]]))
; Mapping between URLs and view functions
(servlet/defservlet journal-servlet
"Eric Lavigne's Journal"
(routes/ANY "/articles/"
(view-article-list))
(routes/ANY "/articles/:title"
(view-article (route :title)))
(routes/ANY "/*"
(http-helpers/redirect-to "/articles/")))
; Server settings
(jetty/defserver journal-server
{:port 80}
"/*" journal-servlet)
; Command to start the server
(jetty/start journal-server)
This first section creates a namespace for my code called “site” and indicates which libraries I am using. I indicate that this file requires code from the compojure.http.servlet namespace, which will be referred to as servlet later in the code. The compojure.http.servlet namespace is defined in compojure/http/servlet.clj of the compojure library, just as the site namespace is defined in site.clj of this project. This naming convention comes from Java packages and enhances Java interoperability.
; Indicates which libraries we need, and the ; shorter names by which they will be called (ns site (:require [compojure.http.servlet :as servlet]) (:require [compojure.http.routes :as routes]) (:require [compojure.http.helpers :as http-helpers]) (:require [compojure.html :as html]) (:require [compojure.html.page-helpers :as page-helpers]) (:require [compojure.server.jetty :as jetty]) (:require [clojure.contrib.sql :as sql]))
Next is a description of the PostgreSQL database, in the format expected by the clojure.contrib.sql library.
(def db {:classname "org.postgresql.Driver"
:subprotocol "postgresql"
:subname "production"
:user "postgres"})
The following code performs a query against the database and returns the results of that query as a list.
; Performs a database query and returns the results as a list
(defn sql-query [query]
(sql/with-connection db
(sql/with-query-results res
; query is string that may contain "?"
; which are replaced with later elements
; in this array
[query]
(into [] res))))
The articles function returns a list of all articles from the database.
; Fetches all articles (defn articles [] (sql-query "select * from article"))
The URL of an article is based on its title, with spaces replaced by dashes and all letters converted to lowercase. For example, “The green snake” would have a URL of “/articles/the-green-snake”. The function article-title-to-url-name would convert “The green snake” to “the-green-snake”, and the function article-url would convert “The green snake” to “/articles/the-green-snake”.
; Converts "My Article" to "my-article" for use in URL
(defn article-title-to-url-name [title]
(.replaceAll
(.toLowerCase title)
"[^a-z0-9]+" "-"))
; Converts map of article attributes to a URL
(defn article-url [article]
(str
"/articles/"
(article-title-to-url-name
(article :title))))
The article function retrieves an article from the database based on the title of the article. This function is very inefficient because it retrieves all of the articles and checks which of them match the given title. The database is capable of doing this job much more efficiently, so this function will need to be rewritten later.
; Fetch an article with the given title
(defn article [title]
(first
(filter
(fn [art]
(=
(article-title-to-url-name
title)
(article-title-to-url-name
(art :title))))
(articles))))
The render-article function uses the compojure.html library to produce an HTML page for an article. It should be easy to understand if you’ve used HTML before.

; HTML page for an article
(defn render-article [article]
(html/html
[:head [:title (article :title)]]
[:body
[:h1 (article :title)]
[:p [:em (article :description)]]
(article :body)]))
The view-article function tries to produce an HTML page for an article with a certain title, redirecting to the list of articles if an article with that title cannot be found.
; Search for article by title, return HTML or redirect
(defn view-article [title]
(try
(render-article (article title))
(catch Exception ex
(http-helpers/redirect-to "/articles/"))))
The render-article-link function produces an HTML snippet to represent a link to an article.
; HTML link to an article
(defn render-article-link [article]
(page-helpers/link-to
(article-url article)
(article :title)))
The view-article-list function produces an HTML page that lists all of the articles with link and description for each.

; HTML page that lists all articles
(defn view-article-list []
(html/html
[:head [:title "Articles"]]
[:body
[:dl (mapcat
(fn [article]
(list
[:dt (render-article-link
article)]
[:dd (article
:description)]))
(articles))]]))
The servlet controls how this application responds to different URLs. The application responds to the “/articles/” URL by listing the available articles, responds to the “/articles/:title” URL (where :title is replaced by the title of an article) by showing that article, and redirects all other URLs to “/articles/”.
; Mapping between URLs and view functions
(servlet/defservlet journal-servlet
"Eric Lavigne's Journal"
(routes/ANY "/articles/"
(view-article-list))
(routes/ANY "/articles/:title"
(view-article (route :title)))
(routes/ANY "/*"
(http-helpers/redirect-to "/articles/")))
; Server settings
(jetty/defserver journal-server
{:port 80}
"/*" journal-servlet)
; Command to start the server
(jetty/start journal-server)
Getting a database setup was a big step in the right direction, but we still need a good way to add new articles. This Compojure application will need an admin panel with forms for easily creating or editing articles, as well as password authentication and SSL for security. These will be the topics of future articles as I prepare for transfering my journal from WordPress to EricLavigne.net.
9 Comments
December 28, 2008 at 7:33 pm
[...] http://ericlavigne.wordpress.com/2008/12/28/using-postgresql-with-compojure/ [...]
December 30, 2008 at 7:04 pm
Great stuff! Now I only need to convert all my old webjure syntax to compojure. Poor hands…
January 1, 2009 at 9:46 pm
Just to point out that it would be better wrapping the page’s html with an html tag, like that: (html [:html [:head …
BTW: nice article, Compojure really need more of them!
January 3, 2009 at 3:01 pm
Soon there will be a standard page layout so that I can place the :html tag in just one place and have it used in all of the views.
January 4, 2009 at 7:05 pm
[...] application that will host my journal on EricLavigne.net. In a previous article, I showed how to use PostgreSQL with Compojure. Now that journal articles are stored in the database, it should be possible to add or update [...]
January 10, 2009 at 4:16 pm
[...] that looks odd but probably saves the experienced user lots of keystrokes. Eric Levigne has written some posts about using Compojure to run a [...]
January 27, 2009 at 9:59 pm
Updated to work with the latest versions of Compojure (namespace changes) and Clojure-contrib (sql/with-query-results API change).
May 5, 2009 at 5:05 pm
Looks nice. Keep the articles coming! I’m especially anxious to see this evolve as you start to optimize the SQL. For a small number of articles filter approach seems reasonable, but I imagine you’ll alter your SQL at some point to load just the article you need. Of course, if you expose the key in the URL and just hand it to your query you’ll need to worry about SQL injection…
May 5, 2009 at 7:13 pm
Christian,
I like pretty URLs, so I liked the idea of generating a URL title by replacing a few problematic characters in the real title.
If I made another column to hold the URL title, I could directly query for the record that I want. I didn’t do that because, when I wrote this article, clojure.contrib.sql did not yet support parameterized queries. It would have been my job to defend against SQL injection, and I didn’t want to do that. The library has improved since then, but I haven’t found time to update the article yet.