A Simple Web Application with Clojure and CouchDB
by Vijay Kiran
Here’s another very small step in my pursuit of learning Clojure. A couple of weeks ago Heroku introduced their new Cedar stack which adds Clojure support on their platform. With my usual curiosity for all things Clojury, I checked out their documentation on how to setup a simple web application written using Compojure and uses PostgreSQL as backend.
I succeeded in following the tutorial and made a simple working prototype even with my limited intelligence. But PostgreSQL seemed old-skool, since all the new cool kids are using NoSQL. So I started changing the backend to use CouchDB instead of PostgreSQL. So with great anticipation of criticism of my Clojure & CouchDB skills, I present how I did it.
The code I used as the starting point is the Shouter app which is explained in much detail in the Heroku’s documentation here. It is a “twitter-clone” (note the quotes!), which is built using Compojure and Hiccup. The code follows clear MVC pattern which made my task of switching the backend extremly easy.
Before I explain what I did in the code, first I need to give a heads-up on pre-requisites, there’s only one if you have a Clojure development installed, and that is CouchDB. Since we are planning to switch the backend to CouchDB the most important thing I needed was – CouchDB! So I went to the Couchbase site and downloaded the super-awesomely-quick-one-click Couchbase Server. This is just a small app you need to run which sits silently in your menubar running the CouchDB Server in the background. Very cool.
Once the couchbase server is ready, next step is to setup the project using lein using the command lein new couch-shouter. Once lein command finishes its business and creates the project, open the project.clj in Emacs. We need to add the required dependencies: clutch – for connecting to CouchDB, compojure & ring – to run the server, hiccup – for HTML generation. Here’s the project.clj in its entirety:
(defproject couch-shouter "0.0.1" :description "Shouter App with CouchDB" :dependencies [[org.clojure/clojure "1.2.1"] [com.ashafa/clutch "0.2.4"] [ring/ring-jetty-adapter "0.3.10"] [compojure "0.6.4"] [hiccup "0.3.6"]])
Once the dependencies are added, I had to make sure that they are loaded into my local lib directory by using the lein deps command. The core.clj file is the starting point of the application which provides the routes and contains the function that starts the jetty server.
Here’s the core.clj – the core of the web application
(ns couch-shouter.core (:use [compojure.core :only (defroutes)]) (:require [compojure.route :as route] [compojure.handler :as handler] [ring.adapter.jetty :as ring] [couch-shouter.controllers.shouts] [couch-shouter.views.layout :as layout])) ;;; Define the routes (defroutes routes couch-shouter.controller.shouts/routes (route/resources "/") (route/not-found (layout/four-oh-four))) ;;; the main application (def application (handler/site routes)) ;;; start to start the jetty server on port (defn start [port] (ring/run-jetty (var application) {:port (or port 8080) :join? false})) ;;; main (defn -main [] (let [port (Integer/parseInt (System/getenv "PORT"))] (start port)))
The source for the Controller and the Views are pretty much copies of the original shouter code. Except that I've removed the CSS and other class declaration for simplicity. Here's the shouts.clj controller:
(ns couch-shouter.controller.shouts (:use [compojure.core :only [defroutes GET POST]]) (:require [clojure.string :as str] [ring.util.response :as ring] [couch-shouter.views.shouts :as view] [couch-shouter.models.shout :as model])) (defn index [] (view/index (model/all))) (defn create [params] (let [shout (:shout params)] (when-not (str/blank? shout) (model/create shout))) (ring/redirect "/")) (defroutes routes (GET "/" [] (index)) (POST "/" {params :params} (create params)))
And here's the listing of the views/layout.clj and views/shouts.clj
(ns couch-shouter.views.layout (:use [hiccup.core :only [html]] [hiccup.page-helpers :only [doctype include-css]])) (defn common [title & body] (html (doctype :html5) [:head [:meta {:charset "utf-8"}] [:meta {:http-equiv "X-UA-Compatible" :content "IE=edge,chrome=1"}] [:title title]] [:body [:div {:id "header"} [:h1 "COUCH SHOUTER"]] [:div {:id "content"} body]])) (defn four-oh-four [] (common "Page Not Found" [:div {:id "four-oh-four"} "The page you requested could not be found!"]))
(ns couch-shouter.views.shouts (:use [hiccup.core :only [html h]] [hiccup.page-helpers :only [doctype]] [hiccup.form-helpers :only [form-to label text-area submit-button]]) (:require [couch-shouter.views.layout :as layout])) (defn shout-form [] [:div {:id "shout-form" } (form-to [:post "/"] (label "shout" "What do you want to SHOUT?") [:br] (text-area "shout") [:br] (submit-button "SHOUT!"))]) (defn display-shouts [shouts] [:div {:id "shouts"} [:ul (map (fn [shout] [:li (:data (:doc shout))]) (:rows shouts))]]) (defn index [shouts] (layout/common "COUCH SHOUTER" (shout-form) [:br] (display-shouts shouts)))
And now lets get to the nub of the matter, the model layer. First up is the model base, which provides the connection to the CouchDB Database:
(ns couch-shouter.models.base (:require [clojure.string :as str] [com.ashafa.clutch :as clutch]) (:import (java.net URI))) (defn database-resource [] (let [url (URI. "http://127.0.0.1:5984/") host (.getHost url) port (if (pos? (.getPort url)) (.getPort url) 443)] (merge {:host host} {:port port} {:language "Clojure"} (if-let [user-info (.getUserInfo url)] {:user (first (str/split user-info #":")) :password (second (str/split user-info #":"))})))) (defn db [] (clutch/set-clutch-defaults! (database-resource)) (clutch/get-database "couch-shouter"))
As you can see above, the database-resource function is parsing the URI for the host, username, password, and other details needed for connecting to the database. There's no username or password information yet, but if you are deploying this app to Heroku with Cloudant then you'll get this information from the CLOUDANT_URL environment variable.
Anyway, let me show you how the shout model has been changed.
(ns couch-shouter.models.shout (:use [couch-shouter.models.base :only [db]]) (:require [com.ashafa.clutch :as clutch])) (defn all [] (clutch/with-db (db) (clutch/get-all-documents-meta {:include_docs true}))) (defn create [params] (clutch/with-db (db) (clutch/create-document {:data params})))
That's it and the shouter app now runs with CouchDB. The source code of the app is now on Github at couch-shouter project.
That's all for now and come back soon for the next experiment.
