Available for hire
Clojure Blog Tutorial

Lets create a simple blog in clojure. I think it should

  • Adhere to an MVC standard
  • Have an Admin interface with basic authentication
  • be backed by MySQL
  • have Views/Templates (we’ll use mustache)

The source code can be found on Github

First install Leiningen so we can start to build a clojure app!

Create the project

$ lein new compojure blog

Compojure is a nifty clojure library that provides some routing. It’ll parse the path in the URL and call a function. The above command will give us a basic scaffold which we can begin to build our app around. The most important directory is the /src directory, this is where we’ll put all of our code.

First lets start the server

$ lein ring server

Go to http://localhost:3000 in your browser and make sure it works. You should see a Hello World message!

Lets get started!

First lets run through the target state anatomy of the app

There will be a couple of files and directories we will work with to adhere to MVC, most aren’t there yet, but we’ll create them as we move forward

/src/blog/handler.clj

Our routes will go in handler.clj, this is also the main file that gets called when we run our server. It’ll look like this after the generation

/src/blog/handler.clj
1
2
3
4
5
6
7
8
9
10
11
12
(ns blog.handler
  (:use compojure.core)
  (:require [compojure.handler :as handler]
            [compojure.route :as route]))

(defroutes app-routes
  (GET "/" [] "Hello World")
  (route/resources "/")
  (route/not-found "Not Found"))

(def app
  (handler/site app-routes))

/src/blog/controllers/

This directory will hold all of all controllers and actions which will be called from the handler.clj routes file

/src/blog/models/

This directory will hold the models which will store business logic and more importantly a gateway to external data, be it from a database, document store or a web service.

/resources/views/

This directory will hold our moustache views.

Routes

In the /src/blog directory you should see a handler.clj file, this is where we’re going to store our routes, and we’ll create a few directories where we store controllers, models and views. We’ll do this a little in this tutorial.

If you open up handler.clj you’ll find a route

1
(GET "/" [] "Hello World")

Breaking this down, it’s listening to a GET request, at path “/” in the URL, it’s not expecting any params [] to be past through and it’ll return the response “Hello World”. You could change “Hello World” to be another string, reload your browser to see the change.

Create a posts controller & index action

Each controller will respond to several RESTful actions, “index, show, new, create, edit, update, destroy”. If coming from an MVC framework these will be very familiar, if not hang in there, it’ll make sense soon.

Lets start by create the directory /src/blog/controllers/ and a new file in that directory called `posts.clj’.

For now, lets start by creating a function that we’ll use to respond to the root path “/”.

/src/blog/controllers/posts.clj
1
2
3
(ns blog.controllers.posts)

(defn index [] "Hello, from index")

Index action to start with

Now lets tell the route “/” to use the newly created index action in the posts controller. To do this we’ll do two things in the handler.clj

  1. Import the controller so all actions/functions are available in our handler.clj file
  2. In place of the string “Hello World” have the “/” route use our new index action

Below are the two changes:

/src/blog/handler.clj
1
2
3
4
5
6
7
8
9
10
11
12
13
(ns blog.handler
  (:use compojure.core)
  (:require [compojure.handler :as handler]
            [compojure.route :as route]
            [blog.controllers.posts :as posts-controller])) ;<-- add this line

(defroutes app-routes
  (GET "/" [] (posts-controller/index)) ;<-- update this line
  (route/resources "/")
  (route/not-found "Not Found"))

(def app
  (handler/site app-routes))

Start the server again and load the app, you should now see “Hello, from index”

$ lein ring server

Create the template for the posts, index controller

So that was easy, with a few lines of code we’ve managed to render a controller with a string, although easy, it wasn’t very impressive.. It would be much more impressive if we could render a template file full of html in place of our “Hello, from index” String.

To do this there will be four things we need to do:

  1. Add the clostache to the project dependencies
  2. Create the mustache template
  3. Add some render helper methods
  4. Modify the posts controller index function to render the template

Lets do it.

In the project.clj file at the root of your project you need to add the dependency for clostache. This will let lein know what dependencies to download to run the application

/project.clj
1
2
3
4
5
6
7
8
9
10
11
(defproject blog "0.1.0-SNAPSHOT"
  :description "FIXME: write description"
  :url "http://example.com/FIXME"
  :dependencies [[org.clojure/clojure "1.5.1"]
                 [compojure "1.1.6"]
                 [de.ubercode.clostache/clostache "1.3.1"]]  ;<-- add this line
  :plugins [[lein-ring "0.8.10"]]
  :ring {:handler blog.handler/app}
  :profiles
  {:dev {:dependencies [[javax.servlet/servlet-api "2.5"]
                        [ring-mock "0.1.5"]]}})

Next lets create the directory /resources/views/posts/ where we’ll store the templates and create the template index.mustache

/resources/views/posts/index.mustache
1
2
3
4
5
6
7
8
9
10
<html>
    <head>
        <title>Hello World</title>
    </head>
    <body>
        <div class="container">
            <h1>Hello, {{ name }}</h1>
        </div>
    </body>
</html>

Add the render methods:

In order to keep the codebase DRY we should be adding these to a controller-helper file, but for the purpose of this tutorial we’ll add them to the /src/blog/controllers/posts.clj file

The below functions will read a template and render it, beware that if the file doesn’t exist it’ll render a

/src/blog/controllers/posts.clj
1
2
3
4
5
6
7
8
9
10
11
12
(ns blog.controllers.posts
  (:require
    [clostache.parser :as clostache]))

(defn read-template [template-name]
  (slurp (clojure.java.io/resource
    (str "views/posts/" template-name ".mustache"))))

(defn render-template [template-file params]
  (clostache/render (read-template template-file) params))

...

Now simply we’ll update the index action in our posts controller, and swap out the "Hello, from index" to the below

/src/blog/controllers/posts.clj
1
2
3
4
...

(defn index []
  (render-template "index" {:name "clojure developer"}))

Reloading the webpage you should see that our HTML has rendered with our template replacement variable “name”

But what about the data? Lets add a model

Here we’ll need to do a few things:

  1. Add the mysql dependencies in project.clj
  2. Create a db, and a posts table
  3. Create a posts model
  4. Fill it with some methods to help pull content out of a db
  5. update the index.mustache file
  6. update the posts.clj to pass to use the model

Migrations are out of scope of this tutorial, so create a blog database with a posts table, the table should have the following fields:

1
2
3
4
5
id:integer
title:varchar(254)
body:text
created_at:datetime
updated_at:datetime

Now we have a database, add the following jdbc & mysql connector dependencies to our project.clj file

/src/project.clj
1
2
3
4
5
6
7
8
9
10
11
12
13
(defproject blog "0.1.0-SNAPSHOT"
  :description "FIXME: write description"
  :url "http://example.com/FIXME"
  :dependencies [[org.clojure/clojure "1.5.1"]
                 [compojure "1.1.6"]
                 [org.clojure/java.jdbc "0.3.0-alpha5"]       ;<-- add this line
                 [mysql/mysql-connector-java "5.1.25"]        ;<-- add this line
                 [de.ubercode.clostache/clostache "1.3.1"]]
  :plugins [[lein-ring "0.8.10"]]
  :ring {:handler blog.handler/app}
  :profiles
  {:dev {:dependencies [[javax.servlet/servlet-api "2.5"]
                        [ring-mock "0.1.5"]]}})

Next lets add the directory /src/blog/models/ with the file posts.clj

In the posts.clj file we need to add some requires statements, the mysql-db connector and the all function.

/src/blog/models/posts.clj
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
(ns blog.models.posts
  (:refer-clojure :exclude [get])
  (:require [clojure.java.jdbc :as j]
            [clojure.java.jdbc.sql :as s]))

(def mysql-db {:subprotocol "mysql"
               :subname "//localhost:3306/blog"
               :user "root"
               :pass " "
               :zeroDateTimeBehaviour "convertToNull"})

(def now
  (str (java.sql.Timestamp. (System/currentTimeMillis))))

(defn all []
  (j/query mysql-db
    (s/select * :posts)))

Update the index.mustache file to show the blog posts

/resources/views/posts/index.mustache
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
<html>
  <head>
    <title>Clojure Blog</title>
  </head>
  <body>
    <div class="container">
      <h1>Listing Posts</h1>
      <sections>
      {{#posts}}
        <h2>{{title}}</h2>
        <datetime>{{created_at}}</datetime>
        <p>{{body}}<p>
      {{/posts}}
      </sections>
  </div>
  </body>
</html>

Update the posts.clj controller to require the posts-model and change the index action

/src/blog/controllers/posts.clj
1
2
3
4
5
6
7
8
9
10
11
12
13
14
(ns blog.controllers.posts
  (:require
    [clostache.parser :as clostache]
    [blog.models.posts :as posts-model]))                       ;<-- update this list

(defn read-template [template-name]
  (slurp (clojure.java.io/resource
    (str "views/posts/" template-name ".mustache"))))

(defn render-template [template-file params]
  (clostache/render (read-template template-file) params))

(defn index []
  (render-template "index" {:posts (posts-model/all)}))         ;<-- update this list

In your favourite sql editor, add a few test posts. Reload the browser and you should see the posts!

Lets set up an admin controller

What’s the point of having a blog without an admin? Hang on now, we’ll be going a little bit faster with less explanation. But if you’ve got this far I think you’ll get it!

What we’ll be doing here is:

  1. Add an admin controller with CRUD actions
  2. Create the admin routes in a protected-routes function
  3. Add basic auth to the protected-routes
  4. Add the Admin views

Lets add the /src/blog/controllers/admin/posts.clj controller with the actions required to CRUD.

/src/blog/controllers/admin/posts.clj
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
(ns blog.controllers.admin.posts
  (:require
    [clostache.parser :as clostache]
    [blog.models.posts :as posts-model]))

(defn read-template [template-name]
  (slurp (clojure.java.io/resource
    (str "views/admin/posts/" template-name ".mustache"))))

(defn render-template [template-file params]
  (clostache/render (read-template template-file) params))

(defn index []
  (render-template "index" {:posts (posts-model/all)}))

(defn show [id]
  (render-template "show" {:post (posts-model/get id)}))

(defn edit [id]
  (render-template "edit" {:post (posts-model/get id)}))

(defn new []
  (render-template "new" {}))

Updating the handler.clj file to include basic auth, protected and public routes as well as a group of the admin/posts routes, you’ll notice we ommited the create/update actions, and instead handle it in the router with the do function. This is personal choice and won’t always be practical. i.e. if you need to add validation, etc.

/src/blog/handler.clj
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
(ns blog.handler
  (:use compojure.core)
  (:require [compojure.handler :as handler]
            [compojure.route :as route]
            [ring.middleware.basic-authentication :refer :all]
            [ring.util.response :as resp]
            [blog.models.posts :as posts-model]
            [blog.controllers.posts :as posts-controller]
            [blog.controllers.admin.posts :as admin-posts-controller]))

(defn authenticated? [name pass]
  (and (= name "user")
       (= pass "pass")))

(defroutes public-routes
  (GET "/" [] (posts-controller/index))
  (route/resources "/" ))

(defroutes protected-routes
  (GET "/admin" [] (admin-posts-controller/index))
  (GET "/admin" [id] (admin-posts-controller/show id))
  (GET "/admin/posts/new" [] (admin-posts-controller/new))
  (POST "/admin/posts/create" [& params]
    (do (posts-model/create params)
        (resp/redirect "/admin")))
  (GET "/admin/posts/:id/edit" [id] (admin-posts-controller/edit id))
  (POST "/admin/posts/:id/save" [& params]
    (do (posts-model/save (:id params) params)
        (resp/redirect "/admin")))
  (GET "/admin/posts/:id/delete" [id]
    (do (posts-model/delete id)
      (resp/redirect "/admin"))))

(defroutes app-routes
  public-routes
  (wrap-basic-authentication protected-routes authenticated?)
  (route/not-found "404 Not Found"))

(def app
  (handler/site app-routes))

Now lets add basic auth dependency in our project.clj file

/blog/project.clj
1
2
3
4
5
6
7
8
9
10
11
12
13
14
(defproject blog "0.1.0-SNAPSHOT"
  :description "FIXME: write description"
  :url "http://example.com/FIXME"
  :dependencies [[org.clojure/clojure "1.5.1"]
                 [compojure "1.1.6"]
                 [org.clojure/java.jdbc "0.3.0-alpha5"]
                 [mysql/mysql-connector-java "5.1.25"]
                 [de.ubercode.clostache/clostache "1.3.1"]
                 [ring-basic-authentication "1.0.2"]]         ;<-- add this line
  :plugins [[lein-ring "0.8.10"]]
  :ring {:handler blog.handler/app}
  :profiles
  {:dev {:dependencies [[javax.servlet/servlet-api "2.5"]
                        [ring-mock "0.1.5"]]}})

Next we’ll add the admin views.

/resources/views/admin/posts/index.mustache
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
<html>
  <head>
    <title>Clojure Blog</title>
  </head>
  <body>
    <div class="container">
      <h1>Listing Posts</h1>
      <a href="/admin/posts/new">Add</a>
      <sections>
      {{#posts}}
        <h2><a href="/admin/posts/{{id}}">{{title}}</a></h2>
        <datetime>{{created_at}}</datetime>
        <p>{{body}}<p>
        <p>
          <a href="/admin/posts/{{id}}/edit">Edit</a>
          <a href="/admin/posts/{{id}}/delete">Delete</a>
        </p>
      {{/posts}}
      </sections>
    </div>
  </body>
</html>
/resources/views/admin/posts/show.mustache
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
<html>
  <head>
    <title>Clojure Blog</title>
  </head>
  <body>
    <div class="container">
      <h1>Listing Post</h1>
      <sections>
        <h2>{{title}}</h2>
        <datetime>{{created_at}}</datetime>
        <p>{{body}}<p>
        <p>
          <a href="/admin/posts/{{id}}/edit">Edit</a>
          <a href="/admin/posts/{{id}}/delete">Delete</a>
        </p>
      </sections>
    </div>
  </body>
</html>
/resources/views/admin/posts/new.mustache
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
<html>
  <head>
    <title>Clojure Blog</title>
  </head>
  <body>
    <div class="container">
      <h1>Editing Post</h1>
      <form action="/admin/posts/create" method="post">
        <label for="title">Title</label>
        <input name="title"/><br />
        <label for="body">Body</label>
        <textarea name="body"></textarea> <br/>
        <input type="submit"/>
      </form>
    </div>
  </body>
</html>
/resources/views/admin/posts/edit
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
<html>
  <head>
    <title>Clojure Blog</title>
  </head>
  <body>
    <div class="container">
      <h1>Editing Post</h1>
      <form action="/admin/posts/{{post.id}}/save" method="post">
        <label for="title">Title</label>
        <input name="title" value="{{post.title}}"/><br />
        <label for="body">Body</label>
        <textarea name="body">{{post.body}}</textarea> <br/>
        <input type="submit"/>
      </form>
    </div>
  </body>
</html>

Finishing up!

We haven’t documented all CRUD features to keep this post somewhat manageable. Make sure you check out the github project below for all of the source code.

There’s still a lot of work to do, such as styling, rendering of partials, DB based user accounts, and importantly DRYing up the code.

The source code can be found on Github

Thanks for reading.

Comments