Skip to content

Instantly share code, notes, and snippets.

@yogthos
Last active August 29, 2015 14:11
Show Gist options
  • Save yogthos/838a2ab5653835a30b8d to your computer and use it in GitHub Desktop.
Save yogthos/838a2ab5653835a30b8d to your computer and use it in GitHub Desktop.
Guestbook app

Creating a guestbook application

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
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment