install the Leiningen build tool
wget https://raw.githubusercontent.com/technomancy/leiningen/stable/bin/lein
chmod +x lein
create a new project
lein new compojure guestbook
cd guestbook
edit project.clj
and add jdbc dependencies
(defproject guestbook "0.1.0-SNAPSHOT"
:description "FIXME: write description"
:url "http://example.com/FIXME"
:min-lein-version "2.0.0"
:dependencies [[org.clojure/clojure "1.6.0"]
[compojure "1.3.1"]
[ring/ring-defaults "0.1.2"]
[org.clojure/java.jdbc "0.3.6"]
[org.xerial/sqlite-jdbc "3.7.2"]]
:plugins [[lein-ring "0.8.13"]]
:ring {:handler guestbook.core.handler/app}
:profiles
{:dev {:dependencies [[javax.servlet/servlet-api "2.5"]
[ring-mock "0.1.5"]]}})
create a new namespace called src/guestbook/db.clj
for the model
(ns guestbook.db
(:require [clojure.java.jdbc :as sql])
(:import java.sql.DriverManager))
(def db {:classname "org.sqlite.JDBC",
:subprotocol "sqlite",
:subname "db.sq3"})
(defn init-db []
(sql/db-do-commands
db
(sql/create-table-ddl
:guestbook
[:name "varchar(32)"]
[:message "varchar(100)"]
[:timestamp "DATETIME DEFAULT CURRENT_TIMESTAMP"])))
(defn read-guests []
(sql/query db ["SELECT * FROM guestbook ORDER BY timestamp DESC"]))
(defn save-message [message]
(sql/insert! db :guestbook message))
create the page and and handle the POST request with CSRF protection and error handling
(ns guestbook.core.handler
(:require [guestbook.db :as db]
[compojure.route :as route]
[compojure.core :refer :all]
[hiccup.page :refer :all]
[hiccup.form :refer :all]
[ring.util.response :refer [redirect]]
[ring.util.anti-forgery
:refer [anti-forgery-field]]
[ring.middleware.defaults
:refer [wrap-defaults site-defaults]]))
(defn render-guests []
[:ul
(for [{:keys [message name timestamp]} (db/read-guests)]
[:li
[:blockquote message]
[:p "-" [:cite name]]
[:time timestamp]])])
(defn home [{:keys [name message error]}]
(html5
[:head
[:title "guestbook"]]
[:body
[:h1 "Guestbook"]
[:p "Welcome to my guestbook!"]
(render-guests)
[:hr]
(when error [:h3 error])
(form-to [:post "/add-message"]
(anti-forgery-field)
[:p "Name:" (text-field "name" name)]
[:p "Message:" (text-area {:rows 10 :cols 40} "message" message)]
(submit-button "comment"))
[:footer "My awesome guestbook"]]))
(defn error-redirect [message]
(redirect (str "/?error=" message)))
(defn add-message [{:keys [name message]}]
(cond
(empty? name) (error-redirect "Some dummy who forgot to leave a name")
(empty? message) (error-redirect "Nothing to say?")
:else
(do
(db/save-message {:name name :message message})
(redirect "/"))))
(defroutes app-routes
(GET "/" req (home (:params req)))
(POST "/add-message" req (add-message (:params req)))
(route/not-found "Not Found"))
(def app
(wrap-defaults app-routes site-defaults))
And that's it, we have a fully working application in under 80 lines of code.
we can run the app with:
lein ring server
we can now package it to run as a standalone service with:
lein ring uberjar
or for container deployment with:
lein ring uberwar