Patterns & Pitfalls in

David James Humphreys (Juxter) [@davidjhumphreys]
/davidjameshumphreys/patterns-pitfalls-liberator/ [slides]

What is Liberator?


A brilliant library that follows the HTTP graph

  • Hook in to the places you need
  • Sensible defaults for the others
  • So...

... just find the parts that you need to hook in.

Flexibility vs. Flexibility

There is a lot to configure to set up a basic route.

  • It follows the HTTP spec closely (very, very closely)
  • Gives fine-grained control over each step
  • Map of functions
  • Return maps of data to chain to the next step in the graph
  • A lot of preamble to get going*

* if you are really impatient!

Chaining the data

The returned map is merged into the functions that occur afterwards.*

(def simple-get
  (resource {:available-media-types ["text/html"]
             ;; do your db call in here
             :exists?               (fn [ctx] {:data "My data"})
             ;; the keys are merged in
             :handle-ok             (fn [{:keys [data] :as ctx}]

* with a few exceptions and ways to prevent this.

Some patterns

  • Always Be Coercing
  • Accepting & Output Types
  • GET/POST patterns
  • Databases & resources on the request
  • Authentication
  • Build your own patterns
  • Use (fn render [args])

A Basic Get

(defn- json? [ctx]
  (-> ctx
      (= "application/json")))

(def simple-get-with-type
  (resource {:available-media-types ["text/html" "application/json"]
             :allowed-methods       [:get]
             :exists?               (fn exists [ctx] {:data "My data"})
             :handle-ok             (fn ok [{:keys [data] :as ctx}]
                                      (if (json? ctx)
                                        {:data data
                                         :is-json true}


  • Use some library to coerce input data into the correct format
  • Use it everywhere (path- & query-params)
  • All input types form-encoded, JSON &c*
  • There are so many bad things one can do with input

/Prismatic/schema is a great choice

*Use some middleware to make coercion easier

Schema/coerce & Liberator

(defn make-malformed-coercer
  "A wrapper for checking the malformed state using Schema coercers.
  Liberator malformed expects [malformed? {:some data}] to be returned
  from the function."
  (fn malformed? [ctx]
    (let [result (coerce-fn ctx)]
      (log/info result)
      (if (error? result)
        [true (merge result
                     (try (negotiate-media-type ctx)
                          (catch ProtocolException _
                              {:media-type "application/json"}})))]
        [false {:coerced-params result}]))))

little hack to render a result

Define your patterns

(defn do-get
  "A simple get endpoint."
  [check-vals-coercer exists? render & {:as overrides}]
  (merge {:available-media-types
              ["text/html" "application/json"]
          :allowed-methods [:get]
              (make-malformed-coercer check-vals-coercer)
          :exists?         exists?
          :handle-ok       render}

Using it

    (fn [ctx] (-> ctx
                  (coercer/coerce {:expected schema/Str} {mapping})))
    (fn exists? [{:keys [request]}]
      {:data (get-data (:database request))})

A basic Post

Using a similar pattern

(defn do-post
  "A simple post endpoint."
  [check-vals-coercer exists? post! post-redirect? & {:as overrides}]
  (merge post
         {:malformed?     (make-malformed-coercer check-vals-coercer)
          :exists?        exists?
          :post!          post!
          :post-redirect? post-redirect?}

Databases & other resources

Add all of your context-sensitive references into request

  • Environmental settings
    • Databases
    • Queues
    • Render settings
  • Bidi!!!!!!1111one

Use middleware to do it.


No *database* globals please

(defn the-application
  "All of the webapp routes and middleware. By defining in this way we
   can pass in various settings (i.e. to add a database create a middleware
   to wrap the request)"
  (-> #'server-routes
      (wrap-trace :ui true)  ;; <- liberator trace
      (wrap-renderer (-> component-settings :render :global-vars))
      (wrap-database (-> component-settings :database))
      wrap-keyword-params    ;; <- param helpers for schema validation
      (wrap-bidi-handlers build-routes handlers)))

Maybe use components

Pitfalls of Flexibility

  • Sensible* defaults
  • Headers?
    • Access-Control-Allow-Origin
    • Cache-Control
    • Sessions & Cookies
    • &c.

Pitfalls of the graph

service-available? happens first, what about long requests?

:as-response (fn [this ctx] build-ring-response)

Other libraries


/davidjameshumphreys/patterns-pitfalls-liberator/ [slides]