Clojure Web Frameworks And Comparisons
Many people coming to Clojure are often coming from Ruby, Python, or Javascript:
These languages have their own set of web frameworks, both small and large. Examples of web frameworks are Flask and Django for Python and Sinatra and Rails for Ruby.
What typically happens is they learn the basics of Clojure and want to do a more tangible project. Since the majority of people are coming from a web development background and that’s what the majority of people use Clojure for, that’s what they start with:
By the way, if you’re interested in the survey data, you can get it here.
The issue is that there aren’t really any traditional full stack web frameworks for Clojure.
Probably the closest thing you can find to a web framework like Rails in Clojure is Luminus.
However, Luminus may be more accurately described as a project template that helps you make web applications than a web framework.
Luminus is more like a project template because the Clojure community values composing small, modular libraries over large frameworks like Rails or Django. It helps you get started without having to hand pick each library you want to use and glue them together.
On one hand this means you have to build your own library stack from picking libraries, which may be difficult for newcomers. On the other hand you know every part of your web application and can customize it as you see fit.
Plus, composing and using the libraries together in Clojure isn’t nearly as painful as you would think. This gives you a lot of flexibility, unlike something like Ruby on Rails where you’re pretty much forced to use certain libraries chosen for you.
Comparisons to full stack frameworks like Rails/Django
You might be wondering what web development is like if nothing like Rails or Django exists in Clojure.
I personally think the web development story in Clojure is mostly similar to Golang’s, at least on the server side. What this means is you’re probably writing many microservices and having a single page application on the client side.
Even though I mentioned microservices, you’re not at all required to do microservices with Clojure. In fact, it’s often recommended that you don’t start out doing microservices, since it’s far easier to start out doing a monolith and then split it apart later when the boundaries are clear.
Clojure offers a few unique benefits in web development because of clojurescript. Clojurescript allows you to share code between the backend and frontend. This feature is even better than node.js and javascript because clojurescript and clojure have the same standard library. I go into more detail about code sharing in my javascript vs clojure article,.
Essentially what you’d do is write a backend API or services in Clojure use Clojurescript to make a single page app.
I feel that the server side HTML rendering story in Clojure is not nearly as good as what you can get with clojurescript and client side rendering. Plus you can easily add react in when you use clojurescript.
Comparisons to microframeworks like Flask/Sinatra
Server side rendered applications in Clojure more closely resemble Flask and Sinatra than Django or Rails.
Still, there’s nothing packaged and ready to go that is truly analogous to Flask.
One of the most popular web libraries in Clojure, Compojure, is more of a request routing library than a microframework because it doesn’t even come with a web server to serve the requests. You don’t get any default middleware nor even a default 404 page.
The benefit of this is you can pick the components you want and can easily swap them out. For instance, if you don’t like using the jetty server then you use aleph instead. I recommend that beginners just use Jetty and don’t even worry about aleph and async code. Or if you don’t like the way compojure does one-way routing then can choose to use something that gives you bi-directional routing like bidi instead.
Unlike Ruby or Python where you’d be using a web server like webrick in development which isn’t meant for production usage, jetty is production ready. There’s no need to change servers when running in other environments
A benefit of this is your local code and test environments mirror production more closely. I’ve had issues in the past with using thin or unicorn in Ruby that I didn’t have locally with webrick.
Ring
Clojure has an abstraction for http like Rack in Ruby or WSGI in Python called Ring.
Ring is an incredibly simple way to deal with requests and responses.
Requests and responses are represented as a map and you simply chain middleware together that do request or response transformations. Ring middleware are just simply functions that wrap a handler function. It’s functions all the way down.
What libraries should you use?
If you’re wondering what libraries you should pick to get something resembling Sinatra or Flask, here’s what I’d recommend:
[[compojure "1.5.2"]
[ring/ring-core "1.5.0"]
[ring/ring-defaults "0.2.3"]
[ring/ring-jetty-adapter "1.5.0"]]
Once you have a server you should pick a routing library. The most popular one for Clojure is compojure. It’s well maintained, has good documentation, and you can easily find answers for your questions online.
Finally, you should include ring-defaults. It’s a configurable set of default middleware you can include for a site or an API. You’ll get the ability to parse form params and have sessions in your application, for example. The nice thing is the middleware are already put in the right order for you.
An example of a minimal Clojure web application
Using the dependencies I listed above, you can achieve a minimal web application with something like this:
(ns sample
(:require [compojure.core :as compojure]
[ring.adapter.jetty :as jetty]
[ring.middleware.defaults :refer [wrap-defaults site-defaults]]))
(compojure/defroutes app
(compojure/GET "/" [] {:body "Hello World!"
:status 200
:headers {"Content-Type" "text/plain"}}))
(def wrapped-app (-> app
(wrap-defaults site-defaults)))
(defn -main []
(jetty/run-jetty #'wrapped-app {:join? false :port 8080}))
The above gives you routing, basic middleware, and a web server.
Deployment
Deploying Clojure applications is going to be far easier and with less ceremony than Ruby or Python. You can just package everything up into a single uber jar or a war file and ship it to your server.
No need to use anything like capistrano to ship your source files to a server.
I’ll write a post on deploying Clojure applications in the future.
Final Thoughts
I wrote this post in response to a lot of questions I see regarding web frameworks. If you see any errors in this article / have differing opinions or thoughts, please leave a comment.
Finally, at the time of writing, there’s a lot of new work being done on Clojure web frameworks:
Master GitHub Actions with a Senior Infrastructure Engineer
As a senior staff infrastructure engineer, I share exclusive, behind-the-scenes insights that you won't find anywhere else. Get the strategies and techniques I've used to save companies $500k in CI costs and transform teams with GitOps best practices—delivered straight to your inbox.
Not sure yet? Check out the archive.
Unsubscribe at any time.