Building out the web service | RESTful Clojure, Part 3
March 1, 2014
Passing our tests
If you recall from the
last tutorial,
we have a few tests that are currently failing. This is a good thing, because
now we know exactly what we need to do to get the tests passing. For reference,
here are the tests from last time:
We need to expose /users and /lists as JSON api endpoints, and we also need
to have our app send an HTTP 404 response for undefined routes. Since the last
test will be the simplest to pass, let’s go ahead and tackle that one first.
Open up handler.clj and get rid of the code from the dummy counting routes
that we created before, and replace them instead with a handler that only has
a not-found route:
If you’re like me, you might be wondering how the not-found route works. Well,
since we’re dealing with a Clojure library, we’re in luck! Go ahead and start
up a REPL (lein repl from the restful_clojure directory), and we can delve
into the code:
Expecting more? Sorry to disappoint! That is the beauty of lisp - that
something like handling 404s can be handled with a 4 line function.
If you run the tests again, you’ll notice that the last one is passing!
However, we’re creating a JSON string directly in our code - yuck! Before we
move on to implementing the /users and /lists endpoints, let’s introduce
some middleware that will create JSON response bodies from Clojure data
structures.
Wiring custom Ring middleware
In most applications, there are certain concerns (e.g. logging, authentication,
content nedotiation, etc.) that we want to take care of at a high level. In
some frameworks, these concerns are taken care of by a
Front Controller or
plugin. With Ring, however, we can simply wrap our routes in another handler
that does… well, pretty much anything we want it to. Can you feel the power?
I can feel the power.
Middleware
are higher-order functions that take in a handler function and return a handler
function. You can modify the request or the response, or you can perform some
side-effect like logging the request to a file. Let’s extend our application by
adding some extremely basic logging - we’ll log the request map to the
server’s console:
If you fire up the server with lein ring server-headless (or just server
if you’re testing it out locally). And hit it with any URL, you should see your
request map printed out to the console. This can be helpful for debugging in
development but is probably not something that we want in production.
As you can see, writing middleware is not terribly complicated. That doesn’t
mean, however, that we should re-invent the wheel just because we can. For
our application, we want to convert Clojure data structures to JSON responses
(and we probably want to parse JSON request bodies as well). For this we can
use ring-json from James Reeves
(the maintainer of Ring and a number of other projects). Let’s add it to our
project.clj as a dependency:
Now over in handler.clj, we can add the middleware provided by ring-json.
Here’s what the entire file should look like at this point:
With this middleware in place, we are all set to parse JSON request bodies and
serve up JSON responses. Now we are finally ready to implement the core of our
application, starting with the tests.
Persisting to a database
In order to persist our data to a database, we’ll add the JDBC driver for
PostgreSQL, the Korma library (which provides a nice
DSL for querying SQL databases), and
Ragtime for database migrations.
Let’s add the dependencies to project.clj. I’ll go ahead and display the
entire source of the file since we’re adding code in a few places:
Now we can create the directory to hold our migrations and create our initial
migrations.
The migrations are in
the project repo, but we won’t go
into them in detail here. Basically, they create tables for users, lists,
products, and product_lists (and a couple other things). If you’re
following along with the project, you can just copy the files from the repo.
And with that, we should be able to run the initial migration from our VM to
create the users table:
And let’s go ahead and migrate the test database as well:
This will use the comfiguration specified under the :test profile specified
in project.clj for migrating the database. We’re set up. We’re migrated. We’re
ready to code.
Getting to the good stuff
Let’s begin by writing the tests for our CRUD operations on both users and
lists:
The basic tests for lists are pretty similar, so I won’t post them here, but they are
in the tutorial repository. I have also included a users+lists_test.clj for
testing the operations that affect both models. Later in this tutorial we will
take a look at the tests and implementation code for dealing with managing the
items on a list.
Now the implementation for both users and lists is fairly straightforward. We’re
using Korma to simplify database transactions. We can put all of the
boilerplace Korma code that we will use to define the entities in our system in
a restful-clojure.entities namespace. The models themselves will go in
separate namespaces under restful-clojure.models.
Again, since the boilerplate code for lists is not too interesting (and it’s
very similar to the code for users, I am not posting the entire code here. I’ll
only cover what’s different and interesting about lists - adding and removing
items.
You know the drill. We start with the tests:
(By the way, I know that unit tests are not the most riveting read, so please
do not feel beholden to take in every line. I post the tests here in case you
are interested in seeing some examples of ordinary tests in Clojure.)
Although I just pasted all of these tests in at once, keep in mind that when
writing code, I try to only write one (failing) test at a time, then write the
code to implement it, but it would be distracting to have so many tiny code samples
littering the post.
There are a couple of interesting things going on here. First, we want to be
able to associate products with a list when it is created and associate/disassociate
them when the list is updated. Second, when we remove a product from a list, we
want to be sure that we are just breaking the relationship between lists and
products, not deleting the product entirely. Now the application code:
Notice that in the add-product function, we build up a query to execute
against the database directly. This is because we are taking advantage of
Postgres’s Enumerated Types,
which do not play nice with Korma. I am slowly becoming convinced that SQL is best
written in SQL, so if I were to start this tutorial again, I would probably use
something like yesql.
Additionally, when we update a list we compare the set of products that are on
the list at update time to the products that were previously on the list, adding
the new products, and removing the products that are no longer present.
Coming up next…
Whew. That was a lot of code! If you have made it this far, I applaud you. In
the next tutorial,
I will cover adding some basic authentication and authorization
to the API using buddy, which is a very
flexible security library. After that, we’ll create a simple ClojureScript client
to consume our API - after all, what good is a service without anything to use it?
Finally, we’ll deploy the app to a DigitalOcean server
(disclaimer: affiliate link) using nginx as a reverse proxy and SSL terminator.