This is the first in a series of articles on clojure web development. My goal is to show how does it feel to do web development in clojure from a java developer perspective. In this article we will create an empty clojure project and create a simple “hello world” application with tests (actually, we will write the tests first).
Once our application is created we will configure our environment to dynamically reload the code so that we can develop without starting/stopping the server all the time.
Let’s get started
The first thing we need to do is download and install leiningen and create a new project
$lein new hello-world
This will create an empty structure with a project.clj file containing the configuration of the project, a core.clj file in the source and test directories, a README and a conveniently configured .gitignore.
As we want to create a web application we need to add the dependency to ring by editing the project.clj file:
(defproject hello-world "1.0.0-SNAPSHOT"
:description "Basic clojure web app"
:dependencies [[org.clojure/clojure "1.3.0"]
[ring/ring-jetty-adapter "1.0.0"]])
We can check that the configuration is ok by downloading the dependencies via leiningen
$lein deps
Writing the test
The first thing we need now is a minimalist server handler. Let’s write the test first in the test/hello_world/test/core.clj file
(ns hello-world.test.core
(:use [hello-world.core])
(:use [clojure.test]))
(deftest it-says-hello-world
(let [req {}
resp (app req)]
(is (= 200 (:status resp)))
(is (= "Hello, world" (:body resp)))))
We can run the test from the command line with leiningen:
$lein test
Exception in thread "main" java.lang.Exception: Unable to resolve symbol: app in this context (core.clj:11)
Writing the handler
Now that we have seen the test fail, we can implement the server in src/hello_world/core.clj:
(ns hello-world.core
(:use ring.adapter.jetty))
(defn app [req]
{:status 200
:headers {"Content-Type" "text/plain"}
:body "Hello, world"})
Nothing special here, just a function that gets a request and returns a map no matter what the request was. We can run the test again:
$lein test
Testing hello-world.test.core
Ran 1 tests containing 2 assertions.
0 failures, 0 errors.
Running the app from the command line
If we want to run our application from the command line we need to add a main function and tell leiningen to run it when we ask it to run the project in the core namespace and configure the core namespace as the default namespace for lein run. This -main function will run a jetty server in the specified port using ‘app’ as the handler function.
;;in core.clj
(defn -main []
(run-jetty app {:port 3000}))
;;in project.clj
(defproject hello-world "1.0.0-SNAPSHOT"
:description "Basic clojure web app"
:dependencies [[org.clojure/clojure "1.3.0"]
[ring/ring-jetty-adapter "1.0.0"]]
:main hello-world.core)
Now we can run our app
$lein run
Dynamically reloading the handler
The problem with our setup, though, is that the server is not reloading the code and so, we need to start and stop the server everytime we make a change (and we don’t want that) so we need to use the leiningen ring plugin. We add the dependency to the plugin as dev-dependency (leiningen plugins are loaded by adding them as dev-dependencies) and tell ring what function to use as handler (it must be the same as we are using when running the server from -main)
:dev-dependencies [[lein-ring "0.4.5"]]
:ring {:handler hello-world.core/app}
And after doing lein deps (in order to download the new dependencies) we invoke the plugin
$lein deps
$lein ring server
And voila! our dynamically reloading server is working and listening for connections on port 3000
Recap
We have seen how to create a clojure project using the awesome leiningen script. We have also seen how to use the TDD infrastructure that comes with every leiningen clojure project to write a test for our server function (we have not written integration tests but now we know where to start). Finally, we have created a very basic web application and configured our environment to reload it dynamically.
As a java developer, I am impressed at the fact that leiningen is using maven internally (compare the project.clj file with the average maven pom file!). The other thing that impresses me as a java developer is how little “webby stuff” leaks into my code (no HttpServletRequest, HttpServletResponse, etc.).
Will this be the case of the typical “Hello world is easy, everything else is overly complicated” toolchain? We will find out as our little application evolves.