Home > clojure > Compojure, the Repl, and Vars

Compojure, the Repl, and Vars

November 5th, 2010

For a future post, I’ve been working on a simple webapp using Compojure, a simple web framework for Clojure. Working with Compojure interactively in the repl gets mentioned a lot, but never with very satisfying answers. So tonight I worked through it and believe I have a reasonable approach that covers everything I want, namely:

  • The ability to start the server in the background of the repl
  • The ability to stop the server in the repl
  • The ability to reload code, hit refresh on the browser, and see my changes

I’ve seen several posts that have a separate namespace (i.e., .clj file) for running the server. This didn’t seem necessary in my tests. Assmuing we’re in web-app.core (src/web_app/code.clj), define your routes and a run function like this:


(defroutes all-routes
  ... route definitions ...)

(defn run
  [options]
  (let [options (merge {:port 8080 :join? true } options)]
    (run-jetty (var all-routes) options)))


There are a couple semi-interesting things going on here. First, we have some default options for the server. When (run) is called, its default behavior is to run the server on port 8080 and block (because of the join? flag, which is important once we’re in the repl).

Secondly, rather than passing all-routes directly to run-jetty, we pass in (var all-routes) (or the #'all-routes reader macro). This way, when we reload our code, the server is using the Var rather than having its own copy. When the root binding changes, the server picks it up without having to restart (see below for a simple example).

Finally, we can use this in the repl:


  ;;; Load the web-app
  user=> (use 'web-app.core :reload)
  nil

  ;;; Run the server with join? false so it doesn't block the repl
  ;;; Also save the result so we can stop the server later
  user=> (def server (run {:join? false}))
  #'user/server
  2010-11-05 20:46:32.797::INFO:  Logging to STDERR via org.mortbay.log.StdErrLog
  2010-11-05 20:46:32.800::INFO:  jetty-6.1.14
  2010-11-05 20:46:32.903::INFO:  Started SocketConnector@0.0.0.0:8080

  ;;; Now test the webapp, make some changes to the code, and ...
  user=> (use 'web-app.core :reload)
  nil

  ;;; Now make sure the changes took effect. Yay!
  user=> (use 'web-app.core :reload)
  nil

  ;;; If we need to, we can stop the server and restart it too!
  user=> (.stop server)
  nil
  user=> (def server (run {:join? false}))
  #'user/server
  2010-11-05 20:48:02.022::INFO:  jetty-6.1.14
  2010-11-05 20:48:02.046::INFO:  Started SocketConnector@0.0.0.0:8080
  nil
  user=> 


That’s about it. Way better than restarting the repl on every code change. I bet there’s much slicker way to do this in Compojure, but I haven’t found it yet.

Var?

As mentioned above, passing the var of the request handler is key to having changes take effect when the code is reloaded. Here’s a very simple example of this behavior outside of the webapp context:


  user=> (defn greet [] (println "Hi!"))
  #'user/greet
  user=> (greet)
  Hi!
  nil
  user=> (defn call [f] (f))
  #'user/call
  user=> (call greet)
  Hi!
  nil
  user=> (def wrap (partial call greet))
  #'user/wrap
  user=> (wrap)
  Hi!
  nil
  user=> (defn greet [] (println "HEY!"))
  #'user/greet
  user=> (wrap)
  Hi!
  nil

  ;;; Note that it still prints "Hi!".
  ;;; Let's try that again, but use var instead

  user=> (def wrap (partial call (var greet)))
  #'user/wrap
  user=> (defn greet [] (println "Hi!"))
  #'user/greet
  user=> (wrap)
  Hi!
  nil
  user=> (defn greet [] (println "Hey!"))
  #'user/greet
  user=> (wrap)
  Hey!
  nil

  ;;; This time the change to greet took affect!


clojure , ,

  1. Jarppe
    January 21st, 2011 at 12:22 | #1

    I’m have been looking for this information for some time now. Thanks for great article.

  1. November 6th, 2010 at 19:09 | #1