Following my now dated "Reddit Clone" tutorial, I've made a revised version which demonstrates how easy it is to build interactive websites using Moustache, Enlive and ClojureQL.
After many requests I've decided it would be a good idea to build a Clojure Driven Social Media Community website using some of the hottest tools available today to help us pull together. If you've read my old "Reddit Clone in 10 minutes and 91 lines of Clojure" this is on a very similar thread, however the core is now 98 lines and the tools have been updated. Lets get to it.
We begin by simply getting a webserver (Jetty) up and running and set to render our frontpage. One of the great things about developing webapps using Ring/Moustache is that you can construct your source such that when loaded on your development machine, it'll spawn a Jetty instance, but when loaded as a war file it will run as a Servlet within Tomcat. This makes for insanely rapid development/deployment. First, we'll start building our namespace which contains all of our web templates - I usually start by building a master page, which all (most) other pages are derived from:
<!DOCTYPE html> <html> <head> <meta http-equiv="Content-Type" content="text/html; charset=UTF-8" /> <title>Clojure Driven Social Site</title> <script type="text/javascript" src="/scripts/jquery.js"></script> <script class="import" type="text/javascript"></script> <link rel="stylesheet" href="/css/main.css" type="text/css" media="screen" /> <link class="style" rel="stylesheet" type="text/css" media="screen" /> </head> <body> <div class="header"> <img src="/images/logo.jpg"/> </div> <div id="content"> </div> <div class="footer"> <a href="/">home</a> <a href="/submit">submit a link</a> <a id="loginout"/> </div> </body> </html>
This HTML serves as a skeleton where Enlive will inject the dynamic components. Notice the places holders for scripts, styles and content which Enlive will look for. What we want now, is a function which takes 3 arguments: The scripts and styles to be loaded in head and the content for the div tag with that id. In addition, we need to pass a 4th argument, which is the user session so that the Login link, becomes a Logout link if the user is already logged in - Enlive makes this trivial:
(ns socialsite.templates (:use net.cgrand.enlive-html)) (deftemplate page "page.html" [session styles scripts cnt] [:link.style] (clone-for [style styles] (set-attr :href style)) [:script.import] (clone-for [script scripts] (set-attr :src script)) [:a#loginout] (content (if (seq (:nick session)) {:tag :a, :attrs {:href "/logout"}, :content ["logout"]} {:tag :a, :attrs {:href "/login"}, :content ["login"]})) [:div#content] (content cnt))
As you can see, deftemplate defines a template with the name 'page' based off the HTML file page.html and takes our 4 arguments which we can use in the body. In the body I use clone-for whenever I need to duplicate an item a certain number of times, set-attr when I need to set attributes and content when Im setting content. Enlive is fairly well documented, but for now all you need to know is that clone-for works exactly like for, so the above says "clone for every style in styles" etc.
If you're new to Enlive you're probably wondering about the way Im setting the login/logout text and its strictly a performance concern. Instead of passing an html-snippet, Im passing Enlives native datastruct, here's how I arrived at it:
socialsite.templates> (html-snippet "<a href="/login">Login</a>") ({:tag :a, :attrs {:href "/login"}, :content ("Login")})
Simply replace the lists with vectors or quoted lists and put that in your templates. With the template in place, we can write our handler:
(defn view-frontpage [r] (->> (page (:session r) nil nil "Welcome") response))
The call to (page) returns the HTML as a seq of strings, passing that to response returns a hashmap which Ring understands. All thats left is defining a route to serve this and launch the server:
(def routes (app (wrap-file "resources") [""] view-frontpage)) (doto (Thread. #(run-jetty #'routes {:port 8080})) .start)
Notice that Im passing the routes argument as a var using #'. This forces Clojure to re-read the value on every read, meaning if I dynamically redef my routes while Im developing the results will show in the browser on the next reload - a must for interactive development. Here's the result:

Looks good - Partly because I used the wrap-file middleware which loads my images/css/scripts etc. You'll notice a big difference from my original Reddit clone in that I have now achieved a 100% separation between code and HTML. Originally I used Hiccup which is a bit like PHP for Clojure in that you seamlessly mix HTML and code. If you want to dig deeper with Enlive, I suggest reading this tutorial.
My original clone persisted everything in memory which was conveniet at the time. However with the release of ClojureQL 1.0.0 it seems fitting to work with a real db backend. So here's what you can do if you have MySQL installed:
mysql> create database social; mysql> grant all privileges on social.* to "social"@"social" identified by "social"; mysql> CREATE TABLE `posts` ( `id` int(11) NOT NULL AUTO_INCREMENT, `title` varchar(255) NOT NULL, `upvotes` int(11) NOT NULL DEFAULT '0', `downvotes` int(11) NOT NULL DEFAULT '0', `submitter` int(11) NOT NULL, `url` varchar(255) DEFAULT NULL, PRIMARY KEY (`id`) ) ENGINE=InnoDB AUTO_INCREMENT=10 DEFAULT CHARSET=utf8; mysql> CREATE TABLE `users` ( `id` int(11) NOT NULL AUTO_INCREMENT, `fullname` varchar(255) NOT NULL, `nick` varchar(255) NOT NULL, `password` varchar(255) NOT NULL, PRIMARY KEY (`id`) ) ENGINE=InnoDB AUTO_INCREMENT=2 DEFAULT CHARSET=utf8; mysql> insert into posts (title,url,submitter) VALUES ("ClojureQL 1.0.0 released!", "http://clojureql.org", 1); mysql> insert into users (fullname,nick,password) VALUES ("Lau Jensen", "Lau", "social");
That will give you a new database called social, a user/pass social/social and two tables posts and users as well as your first user. As a convenience for you guys, I've put this SQL statement in the INSTALL.sql file in the Git repo linked at the bottom. (ps: This is not how you should design a production database!)
Now we need to render the post(s) on the frontpage. In order to do that, we need to join the two tables together and sort them on the amount of upvotes each post has - ClojureQL makes this easy: First we define our connection object and reference our tables
(def db {:classname "com.mysql.jdbc.Driver" :subprotocol "mysql" :user "social" :password "social" :subname "//localhost:3306/social"}) (def posts (table db :posts)) (def users (table db :users))
Then we simply emit the HTML as before:
(defn view-frontpage [r] (->> (frontpage @(-> (join posts (project users [:nick]) (where (= :posts.submitter :users.id))) (sort [:upvotes#desc]))) (page (:session r) ["/css/frontpage.css"] nil) response))
Now the frontpage snippet gets an ordered list of all the posts, which we just need to inject into our skeleton. First the skeleton:
<body> <div class="post"> <div class="voting"> <div class="upvote"> <img src="/images/up.png"/> </div> <div class="votes"/> <div class="downvote"> <img src="/images/down.png"/> </div> </div> <div class="title"/> <div class="submitter"/> </div> </body>
This is quite simple. We have 2 buttons to use for voting a title and a submitter. To fill in these tags we will use another of Enlives features namely the snippet, which allows me to pass a chunk of Enlive-HTML to a template:
(defsnippet frontpage "frontpage.html" [:body :> any-node] [posts] [:div.post] (clone-for [{:keys [id title url nick upvotes downvotes]} posts] [:div.votes] (-> (- upvotes downvotes) str content) [:div.title] (content {:tag :a, :attrs {:href url}, :content [title]}) [:div.submitter] (-> (str "submitted by " nick) content)))
Let see what we've got:

Perfect. Now we need to make the voting work, which unfortuntely involves Javascript - Although I like JS it would be nice with a ClojureDSL for that as well:
<script type="text/javascript"> function vote(id, uri, output) { $.ajax({url: uri, type: 'POST', data: {id: id}, success: function(data) { if (data.substring(0, 2) == "OK") { $(output).html(data.substring(4)); } else { Alert("Your vote could not be submitted"); } } }); } function attach_voter(objs, dir) { $(objs + ' img').each(function() { $(this).click(function() { vote($(this).parent().parent().attr("pid"), dir, $(this).parent().parent().find("div.votes")); }).css("cursor", "pointer"); }); } $(function() { attach_voter("div.upvote", "/vote/up"); attach_voter("div.downvote", "/vote/down"); }); script>
This is a pretty naive implementation:The function vote calls an URL in the backend, receives a response and updates the votes count with whatever it gets back. The function attach_voter finds all image tags under the select objs and adds an onclick event which simply calls vote. This is clever, because if you only want to allow your users to vote once, simply let the backend keep track of the votes and it will always reply with the correct count.
Now we can add the backend implementation:
(defn vote [{id :form-params} direction] (let [predicate (where (= :id (get id "id")))] (update-in! posts (where predicate) (if (= "up" direction) {:upvotes (inc (-> (select posts (where predicate)) (pick! :upvotes)))} {:downvotes (inc (-> (select posts (where predicate)) (pick! :downvotes)))})) (-> (format "OK: %s" (-> (select posts (where predicate)) (aggregate [["(upvotes - downvotes)" :as :sum]]) (pick! :sum))) response)))
Basically this function takes the arguments supplied by the JS and a direction in which to vote, ie. up or down. Then it updates the row in posts where predicate is true. If direction is "up" it increments the upvotes, otherwise increments the downvotes. The function pick! is particularily helpful as it derefences our table, checks if theres a single hit and then pulls out the value of the keyword supplied as the argument - this lets us call inc directly on the result. Once thats done, we want to return the score which is simply upvotes minus downvotes - notice how ClojureQL easily lets me slip in a string and alias that.
Now we need moustache to call this function when somebody votes. As you saw in the JS voting is done by POSTing to either /vote/up or /vote/down, here's how to catch that with Moustache:
(def routes (app (wrap-file "resources") [""] view-frontpage ["vote" direction] (wrap-params (delegate vote direction))))
In a production system, I wouldn't do it like this - I would use the same URI for both calls and carry the direction in the POST data. But this lets me show off more of Moustaches syntax and the delegate function - You could use partial, but then you would have to re-arrange the argument order. The ["vote" direction] will match "/vote/*" where * can be anything except another slash - whatever is passed goes into the var direction.
Now we need to allow users to submit posts, which follows the outline above:
Since the first 2 are exceedingly trivial, I'll simply demonstrate the handler:
(defn accept-submission [req] (let [params (-> req :form-params) user 1];(-> req :session :id)] (conj! posts {:title (params "title") :url (params "url") :submitter user}) (response "OK")))
Because I haven't written the 'login' functionality yet, I've hardcoded the user ID to 1. Then I simply conj! the hasmap containing the values from the post onto posts and reply "OK". Normally, you would want to filter/sanitize your results and add some error handling but thats beyond the scope of this post. Next you might want to group both the POST and GET replies on the URI "/submit", here's how to do it:
(def routes (app (wrap-file "resources") [""] view-frontpage ["submit"] {:get view-submission-page :post (wrap-params accept-submission)} ["vote" direction] (wrap-params (delegate vote direction))))
Passing Moustache a map, instead of a function lets you group related functionality on the same URI.
All we need now is to give users the ability to log in and out. In order to do that, we need to perform the usual steps ending with the handler:
(defn authenticate-user [{params :form-params}] (let [user @(select users (where (and (= :nick (params "usr")) (= :password (params "psw")))))] (if (= 1 (count user)) (assoc (redirect "/") :session (first user)) (redirect "/login?err=true"))))
Ring has a middleware called wrap-session, which when enabled keeps a :session key in every request which you can use to store information about each session. Here I simply select the row from the database which has the correct username/password combination and if I get a hit then I associate that record with the session - If I dont get a hit, I redirect the client to the login page and show an error. This brings us to the final piece of the puzzle...
One of the great delights of working with Ring is its middlewares. They act as functions which work on each request and then passes the request down the line, possibly modified. One middleware which we definitely need now, is a security middleware which rejects unauthorized users from pages they're not allowed to see, here's a naive way to achieve that:
(defn wrap-security [app] (fn [req] (let [uri (:uri req)] (if (or (-> req :session :nick seq) (= uri "/") (= uri "/login")) (app req) (redirect "/?err=Not logged in")))))
If the user is logged in or is trying to access one of the two public URIs the application handles the request as normally, but if not it redirects to the frontpage. The entire middleware stack for this app looks like so:
(def routes (app (wrap-file "resources") (wrap-reload '[socialsite.templates]) (wrap-session) (wrap-security) [""] view-frontpage ["logout"] logout ["login"] {:get view-login-page :post (wrap-params authenticate-user)} ["submit"] {:get view-submission-page :post (wrap-params accept-submission)} ["vote" direction] (wrap-params (delegate vote direction))))
wrap-file allows me to serve static files from the resources directory, wrap-reload reloads the Enlive templates on every pageview, very handy for development, very no-no for production. The final two I've already commented on - Lets see what we ended up with:

There you go, your own Clojure Driven Social Media website to help keep the community engaged and connected :)
The source code for this app is on Github, to launch it simply follow these simple steps:
$ git clone http://github.com/LauJensen/SocialSite.git $ cd SocialSite $ mysql -u root -p < INSTALL.sql Enter password: ******* $ cake repl user=> (use 'socialsite.core) nil
The INSTALL.sql file will create the social/social user/pass on your local MySQL installation as well as the social database which is populates with two tables. Once you issue the use command in the repl, you can access the site on http://127.0.0.1:8080 - Here you can login with my user "Lau" / "social" - Enjoy!
Lau Jensen is the founder and owner of Best In Class a danish consultancy company which specializes in Clojure development.
Lau is also one of the instructors driving the Conj Labs initiative. If you would like to be notified once new blogposts are published, you can follow Lau on Twitter.