Clojure Geek

Writing about learning Clojure

Liberator: As easy as it gets

When I first started using liberator I had a hard time figuring out what was what….and at the same time learning clojure and trying to change my OOP brain into understanding functional code. I want to talk about liberator from that perspective and maybe it will help others too. :)

Liberator is a clojure library to create web APIs. You create your endpoints by defining resources. Liberator needs a routing library to direct traffic to ring (Ring is something like Rack in ruby), Compojure is popular (there are others – liberator’s docs mention bidi … I am going to use compojure today (but bidi looks interesting!). Lets start with a plain compojure app, type:

1
lein new compojure simpleway

It creates a boilerplate web application with ring as the server. Lets try it out:

1
lein ring server

It (usually, unless you have something else running) starts on port 3000, but watch the screen when it starts up. It will say where it started. Then hit http://localhost:3000 to see a hello world message. Ok, it works! :)

Now lets add liberator, you can find out the current version on the project homepage which at this time is 0.13.

add

1
[liberator "0.13"]

To project.clj. Be sure to use double quotes (I have to break this habit of single quoted strings I inherited from years of ruby) and put it anywhere in the list of dependencies. So it looks like this:

1
2
3
4
5
6
7
8
9
10
11
12
13
(defproject simpleway "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"]
                 [liberator "0.13"]
                 [ring/ring-defaults "0.1.2"]]
  :plugins [[lein-ring "0.8.13"]]
  :ring {:handler simpleway.handler/app}
  :profiles
    {:dev {:dependencies [[javax.servlet/servlet-api "2.5"]
                          [ring-mock "0.1.5"]]}})

Ok thats all we need to change there. Open src/simpleway/handler.clj and add the following to the ns at top:

1
[liberator.core :refer [defresource]]

I add it as the second to last thing in the :require statement. It looks like this:

1
2
3
4
5
(ns simpleway.handler
  (:require [compojure.core :refer :all]
            [compojure.route :as route]
            [liberator.core :refer [defresource]]
            [ring.middleware.defaults :refer [wrap-defaults site-defaults]]))

Ok, lets make our first resource:

add this to your file

1
2
(defresource hello-world-resource
  :handle-ok "Hello Clojure World!")

Add it before defroutes app-routes

then change that to read like this:

1
2
3
(defroutes app-routes
  (GET "/" [] hello-world-resource)
  (route/not-found "Not Found"))

changing the GET function to use your resource instead of just spitting out a string. Uhoh .. when you visit http://localhost:3000 you’ll see

1
No acceptable resource available.

Let’s tell the hello-world-resource what media types are available

1
2
3
(defresource hello-world-resource
  :available-media-types ["text/plain"]
  :handle-ok "Hello Clojure World!")

This is something I didn’t realize at first.. in a defresource each thing (or pairs of things, always in a pair) falls into one of four categories as best I can figure out:

  • representation (either as :available-media-types (vector of strings) or :as-response (fn )
  • decision points (ends in ? or starts with [accept | if | is | method | post | put]
  • handler (starts with handle if a redirect also defines :location [url])
  • actions (:get!, :post!, :delete!, :patch!)

So now you have the simplest liberator resource which just returns text.

My thanks to Dar for his talk at AustinClojure which helped solidify some of these things :)

Comments