Oddball HTTP Requests in Compojure

by Alex Nelson, 18 January 2014

As a web-developer coding in Clojure, I want to use Compojure and Ring, specifically to handle various RESTful situations. But how can I write a PUT Compojure route, and jQuery AJAX request? It was trickier than it seems, and no one really seems to discuss it.

There are three things to examine: the Compojure routes, the Ring middleware, and the jQuery requests. Lets consider them in turn, then put them all together for a minimal working (toy) example.

Compojure Methods

Compojure routes are beautifully simple. Setting up PUT or DELETE routes reduce to a triviality at this stage. So we may set up some toy Compojure routes:

(ns rest-example.routes
  (:require [compojure.core :refer :all]
            [compojure.route :as route]
            [rest-example.book :as book]))

(defroutes app
  (GET "/" [] "<h1>Hello World!</h1>")
  (PUT "/books/:id" r (book/put-book r))
  (DELETE "/books/:id" r (book/delete-book r))
  (GET "/books/:id" r (book/get-book r)))

Ring Difficulties

The Ring middleware is actually (surprisingly) straightforward:

(ns rest-example.middleware
  (:require [ring.middleware.params :refer :all]
            [ring.middleware.keyword-params :refer :all]))

(defn my-middleware [routes]
  (-> routes
      wrap-keyword-params
      wrap-params)) ;; whatever else you do should be appended

jQuery Requests

So, the jQuery request for a PUT, PATCH, or DELETE should look like:

$.ajax({url: url, type: "DELETE", /* ... */});

But if you don’t want to repeat yourself over and over and over, you can add a few lines of Javascript extending jQuery (c.f., lines 8273 of jquery-1.10.2.js for example):

function _ajax_request(url, data, callback, type, method) {
    // shift arguments if data argument was omitted
    if ( jQuery.isFunction( data ) ) {
        type = type || callback;
        callback = data;
        data = undefined;
    }

    return jQuery.ajax({
        url: url,
        type: method.toUpperCase(),
        dataType: type,
        data: data,
        success: callback
    });
};
jQuery.extend({
    put: function(url, data, callback, type) {
        return _ajax_request(url, data, callback, type, "put");
    },
    patch: function(url, data, callback, type) {
        return _ajax_request(url, data, callback, type, "patch");
    },
    delete: function(url, data, callback, type) {
        return _ajax_request(url, data, callback, type, "delete");
    }
});

This will allow you to write lines of code like $.delete(url), and so on. So there will be no need to repeat yourself

Puzzle

The non-standard requests (e.g., PUT, PATCH, DELETE, and friends) are trickier to handle since browsers do not universally support them.

What Compojure does is quite simple: simply do a POST request, with the :form-params have a field "_method" which is "patch", "put", or "delete" (or whatever request you want).

Compojure routes check the form-params, which in turn checks the _method in the HTTP request body. Is there some way to add in _method: DELETE (or PUT or…) directly in jQuery?

Remark. This would actually cause problems with Internet Explorer browsers if we don’t solve this puzzle. (End of Remark)

Combining Everything Together

So, now we have our core.clj which looks like:

(ns rest-example.core
  (:require [ring.adapter.jetty :refer :all]
            [rest-example.routes :as routes]
            [rest-example.middleware :as middleware]))

(def routes (middleware/my-middleware routes/app))

(defn -main []
  (run-jetty #'rest-example/routes {:port 8080}))

The complete working code (which really works!) is available.