Ring with clojure tooling

I’m seeing more and more projects having a deps.edn file, but no project.clj, so it seems that the official Clojure tools gain popularity over Leiningen. Since we’re starting a new project, I though it might be a good time to try it ourselves, but I got stuck pretty much right after making that decision.

Our project is a webapp and we use ring for that so far. During development, I liked the lein-ring plugin as it allows me to set up a development server without much hassle. Code reloading works.

But how do I do that without Leiningen? Or is Leiningen still the better option for this?

Thanks,

Johannes

Are you talking about lein ring server to get a dev server up and running? Whatever server you’re using (http-kit, jetty, immutant…) should have a fn you can run with some minimal config to start the server from within REPL, something like the run-server fn here.

To add code reloading, you need clojure.tools.namespace, and a state manager like mount.

What tools.namespace gives you is namespace graph management, so reloads happen in the right order. What mount gives you is an easy way to say “repeatedly start/stop my server (and DB connection, and other stateful things) without breaking things”. So combine the two and you can stop server, reload code, restart server. If you’re talking about live code compile/reload for the frontend, that’s a different thing entirely (check out shadow-cljs or figwheel.main).

To see how to fit these things together, try lein new fulcro example, and check out:

  • src/main/example/server_components/http_server.clj
  • src/dev/user.clj
  • deps.edn

At work we use deps.edn for everything and we have many Clojure web apps. Mostly they use either http-kit or jetty (and we can actually choose which one to start at runtime, based on an environment variable).

Our workflow is usually to start the server process from within the REPL. We use Stuart Sierra’s Component library to manage the start/stop lifecycle of the web server (and everything else in our app). Mount uses global state – it’s “easy” but not “simple” (because global, mutable state is never simple). Component and Integrant avoid global state so they are “simple” but not quite as “easy”.

We typically have a (comment ,,,) form at the bottom of each main namespace that contains Clojure expressions that we can evaluate to create and start the Component for the app (mirroring what happens in -main but usually simpler – omitting monitoring agents etc).

Having started the server process, we edit code and evaluate it into the running app as we make each change. We don’t need to “reload” anything – we don’t need tools.namespace, we rarely stop the running component (although we can easily do so since we def it into a global purely for working in the REPL).

This example project might help you understand how we work: https://github.com/seancorfield/usermanager-example/

3 Likes