This tutorial is the second in a series on building a RESTful web service in Clojure. In the first tutorial, we set up a development environment using Puppet and Vagrant, and we created a skeleton Leiningen project. With all of the prerequisites taken care of, now it’s time to get our hands dirty with some code!
Ring
If you have done any web programming in Clojure before, you have probably used Ring. Ring provides a very thin abstraction over the HTTP request->response cycle. It is similar Ruby’s Rack or the Java servlets specification, allowing you to write applications that respond to HTTP requests without having to deal with the low-level details of sockets, request parsing, etc.
In order to make use of Ring in our application, we need to add it as
a dependency in Leiningen’s project.clj
.
With our dependencies declared, let’s fire up a REPL and build a quick server (Leiningen will download the dependencies before launching the repl):
That’s it! We now have a Ring application up and running, albeit a pretty
boring one. If you visit localhost:3000
in a web browser, you should see the
message, “Hello from Ring” displayed.
Ring basics
A Ring application consists of a handler function, which is just a normal
Clojure function that takes a request map and returns a response map. In the
case of the app-handler
function above, we’re binding the request map to the
request
var and returning a response map with :status
, :headers
, and
:body
keys. Let’s go back to the REPL and create a new server that responds
by printing out the request map received as a Clojure map:
Now when you visit localhost:3000
with a browser (or any other HTTP client),
you should see your HTTP request printed as a Clojure map.
As I said above, Ring is a thin abstraction over HTTP. Consider the following HTTP request and response below (with some formatting applied to the response body):
GET /page HTTP/1.1
Host: localhost
Accept: text/plain
HTTP/1.1 200 OK
Date: Sat, 22 Feb 2014 05:21:47 GMT
Content-Type: text/plain;charset=ISO-8859-1
Content-Length: 350
Server: Jetty(7.6.8.v20121106)
{:ssl-client-cert nil,
:remote-addr "0:0:0:0:0:0:0:1",
:scheme :http,
:request-method :get,
:query-string nil,
:content-type nil,
:uri "/page",
:server-name "localhost",
:headers {"accept" "text/plain", "host" "localhost"},
:content-length nil,
:server-port 80,
:character-encoding nil,
:body #<HttpInput org.eclipse.jetty.server.HttpInput@2523a57f>}
You can see how Ring converted our request into a map, which is just a Clojure datatype. We now have all of the power of Clojure to pull apart that request to generate a response.
Okay, so we can create a “Hello World” web server in Clojure. Big deal. Let’s move on to something more useful.
Meet Compojure
Ring is a great library, but it is pretty low-level, and building a web app directly on top of Ring would be only mildly less tedious than herding cats (and probably not nearly as entertaining). One of the beautiful aspects of functional programming in general, and Clojure in particular, is that you can work with very high-level abstractions that are very close to the business domain of whatever application you are writing. Why bother when we can work more abstractly?
Enter Compojure, an excellent library from James Reeves that abstracts away some of the details of Ring and provides us with a simple interface for routing requests. If you have ever used Sinatra in Ruby, Flask in Python, or Slim in PHP, then Compojure will seem pretty familiar.
Before we dive in, there are a couple more options that we should add to
project.clj
to make our development a little smoother with Ring and
Compojure.
Instead of a lengthy introduction, let’s start by playing with some code!
Create restful-clojure/src/restful_clojure/handler.clj
, and use the code from
below to create an application that can count either up or down.
Starting a web server
Now that we have a counting API, let’s get the web server started. Instead of
running locally, we’ll start the server on our VM to mimick a more
production-like environment. Change into the root of the repository (the
directory that contains the Vagrantfile
, and log onto your VM:
If all goes well, you should be able to visit
http://192.168.33.10:3000/count-up/10
on your host machine and see that your
web server does indeed know how to count. One thing that you may have noticed
when you started the server on your VM was the line,
Started nREPL server on port 9998
nREPL allows us to connect to a REPL remotely and interact with our server as it is running. If you want to play around with it, you can connect to the REPL on your VM using a number of tools, including Leiningen:
Now if you reload http://192.168.33.10:3000/count-up/10
in your browser, you
should see “I forgot how to count!” Pretty cool, huh? Just remember that in the
immortal words of Uncle Ben, “With great power comes great responsibility.”
Test-driven development
I am a firm believer in the value of writing tests first. There are several huge advantages that I can identify in TDD:
- When you write the test first, it forces you to think through what the expected functionality is
- Tests serve as technical documentation that describe by example what any given portion of code is supposed to do
- It’s satisfying to see your code transform a failing test into a passing test.
That said, tests are not an excuse to not think about the code you’re writing. Just because a test passes does not mean that the code under test is elegant or efficient. Tests are just a tool in our toolbox to help us craft solid, reliable applications.
Instead of writing tests for our “dummy” counting application, let’s go ahead
and write several tests for the real application that we’ll begin building in
the next tutorial. We know that we are going to have a “users” resource as well
as a “lists” resource that should both return JSON, so let’s go ahead and write
the tests to check that loading “/users” and “/lists” return an HTTP 200
response code with a JSON response body. Remove the existing test in
tests/restful_clojure/
and create tests/restful_clojure/handler_test.clj
with the code below.
We’re using the clojure.test framework for testing because it probably has the lowest barrier to entry of any of the Clojure testing frameworks out there. If you’re used to a tool like Rspec and would like something a little more expressive, I’d recommend looking into Midje. Now let’s run those tests and watch them fail.
lein test restful-clojure.handler-test
lein test :only restful-clojure.handler-test/test-app
FAIL in (test-app) (handler_test.clj:9)
users endpoint
expected: (= (:status response) 200)
actual: (not (= nil 200))
... MORE OUTPUT ...
Ran 1 tests containing 5 assertions.
5 failures, 0 errors.
Perfect - we now know exactly what we need to implement in order to get these tests to pass. In the next tutorial, we’ll start implementing a few features of our RESTful web service, including authentication and authorization, a data model, and a full RESTful interface for our users and lists resources (all with tests, of course).
I would love to get your feedback on these tutorials! Useful or not useful?
Go To
- Part 1: Setup
- Part 2: Getting a web server up and running
- Part 3: Building out the web service
- Part 4: Securing the service