Tech behind Tech

Raw information. No finesse :)

Posts Tagged ‘web

Introduction to Clojure Web Development using Ring, Compojure and Sandbar

with 14 comments


I gave an introduction to clojure web development presentation to bay area clojure user group. As my slides do not have much information, I am writing this blog so everyone can follow and get started with a sample clojure web application using compojure.

Setting Up:

 lein new address_book 

Add Compojure to Project:

Edit project.clj

(defproject address_book "1.0.0-SNAPSHOT"
  :description "Runnering log"
  :dependencies [[org.clojure/clojure "1.2.0"]
                 [org.clojure/clojure-contrib "1.2.0"]
                 [ring/ring-jetty-adapter "0.2.5"]
                 [compojure "0.4.1"]
                 [hiccup "0.2.6"]
                 [sandbar "0.3.0-SNAPSHOT"]
                 [clj-json "0.3.1"]]
  :dev-dependencies [[swank-clojure "1.3.0-SNAPSHOT"]])

Run

lein deps

Test whether our setup works:

Edit src/address_book/core.clj

(ns address-book.core
  (:use [compojure.core]
        [ring.adapter.jetty])
  (:require [compojure.route :as route]))

(defroutes rts
  (GET "/" [] "Address Book!!")
  (route/not-found "Page not found"))

(def application-routes
     rts)

(defn start []
  (run-jetty application-routes {:port 8080
                                 :join? false}))

Goto http://localhost:8080 and you should see “Address Book”

Interactive Development:

In core.clj, change start function to

(defn start []
  (run-jetty application-routes {:port 8080
                                 :join? false}))

Add Static Folders:

mkdir -p public/{css,js}

Create public/index.html

<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Strict//EN"	"http://www.w3.org/TR/xhtml1/DTD/xhtml1-strict.dtd">'
<html xmlns="http://www.w3.org/1999/xhtml" xml:lang="en">	
 <head>		
   <meta http-equiv="content-type" content="text/html; charset=utf-8" />		
   <title>My Address book</title>		
   <script type="text/javascript" src="http://ajax.googleapis.com/ajax/libs/jquery/1.4.2/jquery.min.js"></script>	
 </head>
 <body id="main">    
    <p>      My Address Book    </p>  
 <body>
</html>

Try to access http://localhost:8080/index.html. This will fail, as we never told compojure how to handle static files.

Edit core.clj and replace rts

(defroutes rts
  (GET "/" [] "Address Book!!")
   (route/files "/")
  (route/not-found "Page not found"))

Now when you access http://localhost:8080/index.html, it should display correctly.

Ring:

Three components

1) Handlers:
Handlers are main functions that process a request. We define handlers using defroutes macro.

2) Middleware:
Middleware are functions that could be chained together to process a request. Middleware functions can take any number of arguments, but the spec stats that first argument should be an handler and function should return an handler. An example for middleware is logging all requests that comes to your webserver. Ring and compojure comes with some standard middleware. We will see in next part how to create our own middleware.

3) Adapters:
Adapters are functions could adapt our handler to a web server. We are using jetty adapter to tie our handler to jetty server.

Lets create our Middleware:

Create src/address_book/middleware.clj

(ns address-book.middleware)

(defn- log [msg & vals]
  (let [line (apply format msg vals)]
    (locking System/out (println line))))

(defn wrap-request-logging [handler]
  (fn [{:keys [request-method uri] :as req}]
    (let [resp (handler req)]
      (log "Processing %s %s" request-method uri)
      resp)))

Edit core.clj ns form

(ns address-book.core
  (:use [compojure.core]
        [ring.adapter.jetty])
  (:require [compojure.route :as route]
			[address-book.middleware :as mdw]))

Edit core.clj application-routes def

(def application-routes
     (-> rts
	     mdw/wrap-request-logging))

Now when you access your server, you should see request log messages.

Main sourcecode:

Create address_book/address.clj

(ns address-book.address
  (:import [java.util Date])
  (:refer-clojure :exclude (find create)))

(def STORE (atom {:1 {:id :1 :name "Siva Jagadeesan" :street1 "88 7th" :street2 "#203" :city "Cupertino" :country "USA" :zipsourcecode 98802}}))

(defn to-keyword [num]
  (if-not (keyword? num)
    (keyword (str num))
    num))

(defn random-number []
  (to-keyword (.getTime (Date.))))

(defn create [attrs]
  (let [id (random-number)
        new-attrs (merge {:id id} attrs)]
    (swap! STORE merge {id new-attrs})
    new-attrs))

(defn find-all []
  (vals @STORE))

(defn find [id]
  ((to-keyword id) @STORE))

(defn update [id attrs]
  (let [updated-attrs (merge (find id) attrs)]
    (swap! STORE assoc id updated-attrs)
    updated-attrs))

(defn delete [id]
  (let [old-attrs (find id)]
    (swap! STORE dissoc id)
    old-attrs))

Edit core.clj ns form

(ns address-book.core
  (:use [compojure.core]
        [ring.adapter.jetty])
  (:require [compojure.route :as route]
			[address-book.middleware :as mdw]
            [address-book.address :as address]
            [clj-json.core :as json]))

Add this function to core.clj

(defn json-response [data & [status]]
  {:status (or status 200)
   :headers {"Content-Type" "application/json"}
   :body (json/generate-string data)})

Add these routes to core.clj

(GET "/addresses" [] (json-response (address/find-all)))
(GET "/addresses/:id" [id] (json-response (address/find id)))
(POST "/addresses" {params :params}  (json-response (address/create params)))

Replace your public folder with https://files.me.com/sivajag/sh35fq

This folder has needed css and js files.

Now go to http://localhost:8080/index.html and you should see a address book webapp. You can add and view addresses.

Authentication:

Create src/address_book/auth.clj

(ns address-book.auth
  (:use [sandbar.form-authentication ]
        [sandbar.validation]))

(defrecord AuthAdapter []
  FormAuthAdapter
  (load-user [this username password]
             (cond (= username "example")
                   {:username "example" :password "password" :roles #{:user}}))
  (validate-password [this]
                     (fn [m]
                       (if (= (:password m) "password")
                         m
                         (add-validation-error m "Unable to authenticate user.")))))

(defn form-authentication-adapter []
  (merge
   (AuthAdapter.)
   {:username "Username"
    :password "Password"
    :username-validation-error "You must supply a valid username."
    :password-validation-error "You must supply a password."
    :logout-page "/"}))

Edit core.clj ns form


(ns address-book.core
  (:use [compojure.core]
        [ring.adapter.jetty]
        [sandbar.auth]
        [sandbar.form-authentication ]
        [sandbar.validation ])
  (:require [compojure.route :as route]
			[address-book.middleware :as mdw]
            [address-book.address :as address]
            [address-book.auth :as auth]
            [clj-json.core :as json]))

Add this def to core.clj

(def security-policy
  [#".*\.(css|js|png|jpg|gif|ico)$" :any
   #"/login.*" :any
   #"/logout.*" :any
   #"/permission-denied.*" :any
   #"/addresses" :user
   #"/index.html" :user
   #"/" #{:user}])

Add this route to core.clj

 (form-authentication-routes (fn [_ c] (html c)) (auth/form-authentication-adapter))

Change application routes function in core.clj

(def application-routes
     (-> rts
        (with-security security-policy form-authentication)  		 
        wrap-stateful-session
	mdw/wrap-request-logging))

Now when you access http://localhost:8080/index.html it will take you to login page. You can login using “example” and “password”.

That is it folks. A simple web app using clojure.

I am sure this blog could be improved a lot. Please leave comments I will update this blog with your feedback.

Written by Siva Jagadeesan

January 19, 2011 at 12:36 am

Posted in Clojure

Tagged with , , , ,

Compojure Demystified with an example – Part 6

with one comment


In this part we will start implementing edit and delete functionalities.

We are going to build these services,

Edit address – PUT – http://localhost:8080/addresses/:id
Delete address – DELETE – http://localhost:8080/addresses/:id

1) Adding PUT and DELETE route

Edit routes.clj

(ns address_book.routes
  (:use [compojure.core])
  (:require [address-book.address :as address]
            [address-book.middleware :as mdw]
            [compojure.route :as route]
            [clj-json.core :as json]))

(defn json-response [data & [status]]
  {:status (or status 200)
   :headers {"Content-Type" "application/json"}
   :body (json/generate-string data)})

(defroutes handler
  (GET "/addresses" [] (json-response (address/find-all)))
  (GET "/addresses/:id" [id] (json-response (address/find id)))
  (POST "/addresses" {params :params}  (json-response (address/create params)))
  (PUT "/addresses/:id" {params :params} (json-response (address/update (params "id")  params)))
  (DELETE "/addresses/:id" [id] (json-response (address/delete id )))

  (route/files "/" {:root "public"})
  (route/not-found "Page not found"))

(def address-book
     (-> handler
         mdw/wrap-request-logging))

As you can see it is pretty straight forward to add PUT and DELETE route.

What is up with params?

In previous version of Compojure there were some magic variables, like params, session etc. From version 0.4 there are no more magic variables. Instead Compojure provides a map “params”. “params” map will contain all request information. For some reason, params map have a key :params which contains request parameters. That is why we have do {params :params}. I wish they will rename “params” to something else.

Instead of doing {params :params}, the compojure document states that we could do [params id] and when compojure detects a vector, instead of a map it will assign the parameters to each value of the vector. But for some reason, I was not able to make it work.

Front End code

Checkout from github for front end code. Warning: front end code is bad, do not take this as an example.

In next part we will see how to send flash messages.

Source code is now available at github. Created branches for each part.

Next part is posted.

Written by Siva Jagadeesan

September 16, 2010 at 11:09 pm

Posted in Clojure

Tagged with , , , , ,

Compojure Demystified with an example – Part 5

with 2 comments


In this part lets write our own middleware.

From part4 you will remember,

“Middleware are functions that could be chained together to process a request. Middleware functions can take any number of arguments, but the spec stats that first argument should be a handler and function should return a handler. An example for middleware is logging all requests that comes to your webserver.”

1) Create a namespace for middleware

Create src/address_book/middleware.clj

(ns address-book.middleware)

(defn- log [msg & vals]
  (let [line (apply format msg vals)]
    (locking System/out (println line))))

(defn wrap-request-logging [handler]
  (fn [{:keys [request-method uri] :as req}]
    (let [resp (handler req)]
      (log "Processing %s %s" request-method uri)
      resp)))

Our wrap-request-logging is a middleware which takes a handler and returns an handler.

2) Add our middleware to our chain of handlers

Edit routes.clj

(ns address_book.routes
  (:use [compojure.core])
  (:require [address-book.address :as address]
            [address-book.middleware :as mdw]
            [compojure.route :as route]
            [clj-json.core :as json]))

(defn json-response [data & [status]]
  {:status (or status 200)
   :headers {"Content-Type" "application/json"}
   :body (json/generate-string data)})

(defroutes handler
  (GET "/addresses" [] (json-response (address/find-all)))
  (GET "/addresses/:id" [id] (json-response (address/find id)))
  (POST "/addresses" {params :params}  (json-response (address/create params)))

  (route/files "/" {:root "public"})
  (route/not-found "Page not found"))

(def address-book
     (-> handler
         mdw/wrap-request-logging))

3) Test our middleware

Now start your server and you can see all requests are getting logged. This is very powerful feature. We can do security etc using middleware. Ring and Compojure comes with some useful middleware. Check them out.

In next part we will implement edit  and delete functionalities.

Source code is now available at github. Created branches for each part.

Written by Siva Jagadeesan

September 2, 2010 at 12:19 am

Posted in Clojure

Tagged with , , , , ,

Compojure Demystified with an example – Part 4

with 6 comments


In this part we will start implementing Add,  View and View all functionalities.

The services we are going to build are

View All Addresses – GET – http://localhost:8080/addresses
View single address – GET – http://localhost:8080/addresses/:id
Add Address – POST – http://localhost:8080/addresses

Interactive Development using slime

I mentioned in last part that after every change to our code we need to restart our server for our changes to be reflected. This is a pain and against clojure (lisp) philosophy . It would be great if we could eval our modified buffers in emacs and those changes reflected immediately in our jetty server. Thankfully there is very easy way to do this.

In core.clj we are starting jetty adapter. We can start this jetty server in background using future and reload the namespace that we changed. This way we can do interactive development without restarting our server.

PS: Make sure you have emacs setup with slime. I feel emacs is the best IDE for clojure. Again this is my opinion. There are lot of information on how to do this.

I talked about jetty adapter. It is time for us to look at some what little deeper in Compojure.

Compojure

Compojure is based on a library  called Ring. In fact most of clojure web frameworks are based on Ring. So to understand Compojure, it is important to understand Ring.

Ring has three components.

1) Handlers:

Handlers are main functions that  process a request. We define handlers using defroutes macro.

2) Middleware:

Middleware are functions that could be chained together to process a request. Middleware functions can take any number of arguments, but the spec stats that first argument should be an handler and function should return an handler. An example for middleware is logging all requests that comes to your webserver.  Ring and compojure comes with some standard middleware. We will see in next part how to create our own middleware.

3) Adapters:

Adapters are functions could adapt our handler to a web server. We are using jetty adapter to tie our handler to jetty server.

Refactor code to make it easy for interactive development

Edit core.clj

(ns address_book.core
  (:use [compojure.core]
        [ring.adapter.jetty])
  (:require [compojure.route :as route]))

(defroutes example
  (GET "/" [] "<h1>My Address Book!</h1>")
  (route/files "/" {:root "public"})
  (route/not-found "Page not found"))

(future (run-jetty (var address-book) {:port 8080}))

1) Add swank-clojure to our project

swank-clojure comes with lein plugin which will allow us to start a swank server and from emacs you can connect using slime. There are lot of documentation about swank-clojure and slime. Let me know if you need more information. If I see enough interest, I could blog about it or at least point to some good resources.

Edit project.clj

(defproject address_book "1.0.0-SNAPSHOT"
  :description "Address Book"
  :dependencies [[org.clojure/clojure "1.2.0"]
                 [org.clojure/clojure-contrib "1.2.0"]
                 [compojure "0.4.1"]
                 [ring/ring-jetty-adapter "0.2.3"]]
  :dev-dependencies [[swank-clojure "1.2.1"]])

and run

lein deps

Now you should be able to start swank server using

lein swank

2) Break core.clj into different namespaces

Current core.clj is doing two things. Setting up routes and starting jetty server. Lets break it into web_server.clj and routes.clj

rm src/address_book/core.clj

create src/address_book/routes.clj

(ns address_book.routes
  (:use [compojure.core])
  (:require [compojure.route :as route]))

(defroutes address-book
  (GET "/" [] "<h1>My Address Book!</h1>")
  (route/files "/" {:root "public"})
  (route/not-found "Page not found"))

create src/address_book/webserver.clj

(ns address_book.webserver
  (:use [compojure.core]
        [ring.adapter.jetty]
        [address_book.routes :as routes]))

(future (run-jetty (var address-book) {:port 8080}))

3) At last lets create our Address namespace

In this code, I am going to use atoms for persistence. In future I will probably show how we can persist in mysql db using clj-records.

create src/address_book/address.clj

(ns address-book.address
  (:use [address-book.utils number])
  (:refer-clojure :exclude (find create)))

(def STORE (atom {:1 {:id :1 :name "Siva Jagadeesan" :street1 "88 7th" :street2 "#203" :city "Cupertino" :country "USA" :zipcode 98802}}))

(defn create [attrs]
  (let [id (random-number)
        new-attrs (merge {:id id} attrs)]
    (swap! STORE merge {id new-attrs})
    new-attrs))

(defn find-all []
  (vals @STORE))

(defn find [id]
  ((to-keyword id) @STORE))

(defn update [id attrs]
  (let [updated-attrs (merge (find id) attrs)]
    (swap! STORE assoc id updated-attrs)
    updated-attrs))

(defn delete [id]
  (let [old-attrs (find id)]
    (swap! STORE dissoc id)
    old-attrs))

Create utils/number.clj

(ns address-book.utils.number
  (:import [java.util Date]))

(defn to-keyword [num]
  (if-not (keyword? num)
    (keyword (str num))
    num))

(defn random-number []
  (to-keyword (.getTime (Date.))))

PS: I am not writing tests to keep this blog short. But I would advice everyone to write tests.

4)   Lets update routes for add, view and view all functionalities

</span>
<pre>(ns address_book.routes
  (:use [compojure.core])
  (:require [address-book.address :as address]
            [compojure.route :as route]
            [clj-json.core :as json]))

(defn json-response [data & [status]]
  {:status (or status 200)
   :headers {"Content-Type" "application/json"}
   :body (json/generate-string data)})

(defroutes handler
  (GET "/addresses" [] (json-response (address/find-all)))
  (GET "/addresses/:id" [id] (json-response (address/find id)))
  (POST "/addresses" {params :params}  (json-response (address/create params)))

  (route/files "/" {:root "public"})
  (route/not-found "Page not found"))

(def address-book
     handler)

Compojure comes with GET, POST, PUT, DELETE, HEAD and ANY macro to define routes. These macros are self explanatory. Currently we have used GET and POST to define our routes.

Parsing Parameters

Compojure binds request parameters to params.

To get all parameters from request you can destructure map like we did in

POST "/addresses" {params :params}  (json-response (address/create params)))

To get particular parameters from request you can use Compojure sugar syntax like we did in
[sourceode](GET “/addresses/:id” [id] (json-response (address/find id)))[/sourcecode]

5)  Lets build our front end
PS: front end code is a hack. Please don’t follow these codes as a good practice.

Edit public/index.html

<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.01//EN"
        "http://www.w3.org/TR/html4/strict.dtd">
<html lang="en">
<head>
	<meta http-equiv="Content-Type" content="text/html; charset=utf-8">
	<title>My Address Book</title>
        <link href="/css/address.css" media="screen" rel="stylesheet" type="text/css" />
        <script src="/js/jquery-1.4.2.min.js" type="text/javascript"></script>
        <script src="/js/jquery-ui-1.8.1.custom.min.js" type="text/javascript"></script>
        <script src="/js/jquery.form.js" type="text/javascript"></script>
        <script src="/js/address_book.js" type="text/javascript"></script>
        <script src="/js/address_list.js" type="text/javascript"></script>
</head>
<body>
<div id="wrap">
	<div id="header"><h1>My Address Book</h1></div>

	<div id="main">
          <table id="address">
            <tr><td>Name</td><td id="name"/></tr>
            <tr><td>Street1</td><td id="street1"/></tr>
            <tr><td>Street2</td><td id="street2"/></tr>
            <tr><td>City</td><td id="city"/></tr>
            <tr><td>Country</td><td id="country"/></tr>
            <tr><td>ZipCode</td><td id="zipcode"/></tr>
          </table>
	  <table id="address-list" border="1" width="100%" rules="rows" align="center">
            <tr>
              <th>Name</th>
              <th>Action</th>
            </tr>
            <tr>
              <td >row 1, cell 1</td>
              <td align="center">row 1, cell 2</td>
            </tr>
          </table>
	</div>
	<div id="sidebar">
          <form id="address-form" class="formular" method="post" action="/addresses">
	    <fieldset class="login">
	      <legend>New Address</legend>
	      <div>
		<label for="name">Name</label> <input type="text" id="name" name="name">
	      </div>
              <div>
		<label for="street1">Street1</label> <input type="text" id="street1" name="street1">
	      </div>
               <div>
		<label for="street2">Street2</label> <input type="text" id="street2" name="street2">
	      </div>
              <div>
		<label for="city">City</label> <input type="text" id="city" name="city">
	      </div>
              <div>
		<label for="country">Country</label> <input type="text" id="country" name="country">
	      </div>
               <div>
		<label for="zipcode">ZipCode</label> <input type="text" id="zipcode" name="zipcode">
	      </div>

              <input class="submit" type="submit" value="Create"/>
	    </fieldset>
          </form>
	</div>
	<div id="footer">
	  <p><a href="TechbehindTech.com">TechBehindTech</a> -- Siva Jagadeesan</p>
	</div>
</div>
</body>
</html>

Create public/css/address.css

body,html {
    margin:0;
    padding:0;
    color:#000;
    background:#a7a09a;
}
#wrap {
    width:970px;
    margin:0 auto;
    background: #fffeff;
}
#header {
    padding:5px 10px;
    background: #054477;
	text-align: right;
	color: #fffeff;
}
h1 {
    margin:0;
}
#main {
    float:left;
    width:580px;
    padding:10px;
	background-color: #fffeff;
}
h2 {
    margin:0 0 1em;
	background-color: #fffeff;
}
#sidebar {
    float:right;
    width:350px;
    padding:10px;
    background-color: #fffeff;
}
#footer {
    clear:both;
    padding:5px 10px;
    background: #fed47f;
}
#footer p {
    margin:0;
}
* html #footer {
    height:1px;
}

form * {margin:0;padding:0;} /* Standard margin and padding reset, normally done on the body */

legend {
	color:#000; /* IE styles legends with blue text by default */
	*margin-left:-7px;
	font-weight: bold;
	font-size: 20px;
}
fieldset {
	border:1px solid #dedede; /* Default fieldset borders vary cross browser, so make them the same */
}
fieldset div {
	overflow:hidden; /* Contain the floating elements */
	display:inline-block;
	padding: 10px;
}
fieldset div {display:block;} /* Reset element back to block leaving layout in ie */
label {
	float:left; /* Take out of flow so the input starts at the same height */
	width:8em; /* Set a width so the inputs line up */
}

Create public/js/address_book.js


$(document).ready(function() {
    $("#address").hide();
    $("#address-list").showAddressList();

    $(".address-link").live("click",function(e){
        $.getJSON($(this).attr("href"), function(json) {
            $("#address").showAddress(json);
        });
        e.preventDefault();
    });

    $('#address-form').submit(function(event){
        event.preventDefault();
        var $this = $(this);
        var url = $this.attr('action');
        var dataToSend = $this.serialize();
        var callback = function(data){
            $("#address-list").addAddress(data);
        };
        var options = {
            success:   callback,
            url: url,
            type:     "POST",
            dataType: "json",
            clearForm: true
        };
        $(this).ajaxSubmit(options);
    });

});

Create public/js/address_list.js

function action_links(data){
    var link = "<a href=\"\">edit</a> ";
    link += " | <a href=\"\">delete</a>";
    return "edit | delete";
}

$.fn.showAddressList = function(){
    return this.each(function(){
        var that = this;
        $.getJSON("addresses", function(json) {
            $(that).html(" <tr> <th>Name</th> <th>Action</th> </tr>");
            $.each(json,function(i,data) {
                $(that).append("<tr id=\"address-"+ data.id +" \"><td><a class=\"address-link\" href=\"addresses/" + data.id +"\">" + data.name + "</a></td><td align='center'>" + action_links(data) + "</td></tr>");
            });
        });
    });
};

$.fn.addAddress = function(json){
    var data = json;
    var that = this;
    return this.each(function(){
        var that = this;
        $(that).append("<tr id=\"address-"+ data.id +" \"><td><a class=\"address-link\" href=\"addresses/" + data.id +"\">" + data.name + "</a></td><td align='center'>" + action_links(data) + "</td></tr>");
    });
};

$.fn.showAddress = function(json){
    var data = json;
    var that = this;
    return this.each(function(){
        $(that).slideDown('slow');
        $(that).find("#name").html(data.name);
        $(that).find("#street1").html(data.street1);
        $(that).find("#street2").html(data.street2);
        $(that).find("#city").html(data.city);
        $(that).find("#country").html(data.country);
        $(that).find("#zipcode").html(data.zipcode);
    });
};

Download jquery-1.4.2.min.js, jquery-ui-1.8.1.custom.min.js and jquery.form.js to public/js folder.

6)  Start the web server

[/sourcecode]lein repl src/address_book/webserver.clj [/sourceode]

Wow that was lot of stuff. We will look more into writing our own middlewares in next part.

Source code is now available at github. Created branches for each part.

Part 5 is posted.

Written by Siva Jagadeesan

August 24, 2010 at 4:25 pm

Posted in Clojure

Tagged with , , , , ,

Compojure Demystified with an example – Part 3

with 19 comments


In part2 we saw how to setup a skeleton project with Compojure. In this part we will see how to add static files to a Compojure project.

For our application, we will use JQuery as front end and Clojure as a backend. I will concentrate more on Compojure.

1) Create folders for static files.

mkdir public
mkdir public/css
mkdir public/js

It is better to have static files under a seperate directory that way you could use your webserver to serve these files. For now we will use Compojure to serve these files.

2) Create index.html under public folder

<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Strict//EN"
	"http://www.w3.org/TR/xhtml1/DTD/xhtml1-strict.dtd">
<html xmlns="http://www.w3.org/1999/xhtml" xml:lang="en">
	<head>
		<meta http-equiv="content-type" content="text/html; charset=utf-8" />
		<title>My Address book</title>
		<script type="text/javascript" src="http://ajax.googleapis.com/ajax/libs/jquery/1.4.2/jquery.min.js"></script>
	</head>

  <body id="main">
    <p>
      My Address Book
    </p>
  <body>
</html>

3) Start your server

lein repl src/address_book/core.clj

4) Access index.html

Go to http://localhost:8080/index.html . You will get “Page not found” instead of “My Address Book”. This is happening because we have not told compojure about our static files folder.

Route helper functions in Compojure

In Compojure we have two route helper functions. We have seen one already in core.clj file.

1) not-found :

This function is used to capture all other routes that are not defined in Compojure. As we still have not defined index.html we got “Page not found”.

2) files :

This function is used to inform Compojure where static files are present.

Lets change our core.clj to handle static files

(ns address_book.core
  (:use [compojure.core]
        [ring.adapter.jetty])
  (:require [compojure.route :as route]))

(defroutes example
  (GET "/" [] "<h1>My Address Book!</h1>")
  (route/files "/" {:root "public"})
  (route/not-found "Page not found"))

(run-jetty example {:port 8080})

Restart your server (there is a way to avoid restarting using futures. I will show that in a later blog) and access http://localhost:8080/index.html . Now you should see “My Address Book”.

In this part we saw how we can handle static files. In next part we will implement Add , View and View All functionalities.

PS: Using Hiccup you can create HTML and css in clojure. I will try to cover this in future blogs.

Source code is now available at github . Created branches for each part.

Checkout PART4

Written by Siva Jagadeesan

August 16, 2010 at 9:04 pm

Posted in Clojure

Tagged with , ,

Compojure Demystified with an example – Part 2

with 2 comments


In part 1 I introduced the address book application that we are going to build.

In this part we will setup our skeleton project with compojure.

1) Install Leiningen

http://github.com/technomancy/leiningen/blob/master/README.md

2) Create a new project using Leiningen

lein new address_book

3) Add Compojure to our project:

a) Edit project.clj

(defproject address_book "1.0.0-SNAPSHOT"
  :description "Address Book"
  :dependencies [[org.clojure/clojure "1.1.0"]
                 [org.clojure/clojure-contrib "1.1.0"]
                 [compojure "0.4.1"]
                 [ring/ring-jetty-adapter "0.2.3"]])

b) Install dependencies

lein deps

This should install all dependencies of compojure

c) Test whether our setup is working

Edit src/address_book/core.clj

(ns address_book.core
  (:use [compojure.core]
           [ring.adapter.jetty])
  (:require [compojure.route :as route]))

(defroutes example
  (GET "/" [] "My Address Book!")
  (route/not-found "Page not found"))

(run-jetty example {:port 8080})

Run the server

lein repl src/address_book/core.clj

Goto http://localhost:8080 and you should see “My Address Book!”

In next part we will start implementing our functionalities.

Written by Siva Jagadeesan

August 15, 2010 at 6:49 pm

Posted in Clojure

Tagged with , ,

Compojure Demystified with an example – Part 1

with one comment


In this part, I am going to talk about what application we are going to implement.

Lets build a simple address book web application using compojure. This will be a RESTFUL application. As we go through this example we will see some interesting aspects of compojure.

Functionalities

  • Add / Edit / Delete Address book
  • View All Addresses
  • View a single Address

Domain

Address

  • Name
  • Street1
  • Street2
  • City
  • Country
  • ZipCode

REST interfaces we will implement

Next part 2 we will setup our base project with compojure.

Written by Siva Jagadeesan

August 14, 2010 at 12:15 pm

Posted in Clojure

Tagged with , ,

Follow

Get every new post delivered to your Inbox.

Join 146 other followers

%d bloggers like this: