Skip to main content

Setup-for-development

There are different options to optimize your development workflow:

  • Reload namespaces on source file changes
    • Setup with lein-ring plugin
    • Setup with boot
  • Use a module lifecycle library [[Interactive Development]]

The first approach is fast, simple to setup. The ring-devel library provides middleware for this purpose.

Downside, namespace reloading does not reload application state. Consequently, there can be situation requiring to restart the server.

Setup using the lein-ring plugin

The Lein-Ring plugin is the most straightforward way. Create two files

  • src/sample.clj
  • project.clj

A simple service returning 'Hello World' in src/sample.clj

(ns sample)

(defn handler [request]
{:status 200
:headers {"Content-Type" "text/plain"}
:body "Hello world."})

Add lein-ring and configure the ring handler to your project file, project.clj:

:ring {:handler sample/handler}

A minimal project.clj:

(defproject lein-demo "0.1.0-SNAPSHOT"
:dependencies [[org.clojure/clojure "1.8.0"]
[ring/ring-core "1.6.3"]
[ring/ring-jetty-adapter "1.6.3"]
[ring/ring-devel "1.6.3"]]
:ring {:handler sample/handler}
:plugins [[lein-ring "0.12.5"]])

Start a development server:

  lein ring server    

Visit the server at http://localhost:3000

The server will automatically reload any modified files in your source directory.

Setup using Clojure deps and CLI

Clojure provides command line tools for transitive dependency graph expansion and the creation of classpaths. Visit https://clojure.org/guides/deps_and_cli for more information.

Create the following project file structure:

.
├── deps.edn
├── dev
│ └── hotreload.clj
└── src
└── sample
└── server.clj

Describe the dependencies and classpaths.

;; deps.edn
{:paths ["src"]
:deps
{org.clojure/clojure {:mvn/version "1.9.0"}
ring/ring-core {:mvn/version "1.6.3"}
ring/ring-jetty-adapter {:mvn/version "1.6.3"}}
:aliases
{:dev
{:extra-paths ["dev"]
:extra-deps {ring/ring-devel {:mvn/version "1.6.3"}}
:main-opts ["-m" "hotreload"]}}}

Create the production server.

;; src/sample/server.clj
(ns sample.server
(:require [ring.adapter.jetty :refer [run-jetty]])
(:gen-class))
(defn handler [request]
{:status 200
:headers {"Content-Type" "text/plain; charset=UTF-8"}
:body "hello world!\n"})
(defn -main [& args]
(run-jetty handler {:port 3000}))

Start the server with clojure -M -m sample.server. Visit http://localhost:3000 and confirm the string "hello world!" displays.

Create the development server.

;; dev/hotreload.clj
(ns hotreload
(:require [ring.adapter.jetty :refer [run-jetty]]
[ring.middleware.reload :refer [wrap-reload]]
[sample.server :refer [handler]])
(:gen-class))
(def dev-handler
(wrap-reload #'handler))
(defn -main [& args]
(run-jetty dev-handler {:port 13000}))

Start the server with clojure -A:dev -M:dev. Visit http://localhost:13000 and confirm the string "hello world!" displays. Note, newer Clojure CLI versions do to need the extra -A:dev part.

The server will automatically reload any modified files in your source directory.

Setup using boot

Boot is a build tool for Clojure', https://boot-clj.github.io/

Create two files

  • build.boot
  • src/sample.clj

A minimal build.boot

(set-env!
:resource-paths #{"src"}
:dependencies '[[org.clojure/clojure "1.8.0"]
[ring/ring-core "1.6.3"]
[ring/ring-jetty-adapter "1.6.3"]
[ring/ring-devel "1.6.3"]])

(deftask dev
"Run server hot reloading Clojure namespaces"
[p port PORT int "Server port (default 3000)"]
(require '[sample :as app])
(apply (resolve 'app/run-dev-server) [(or port 3000)]))

A simple service returning 'Hello World' without reloading in src/sample.clj

(ns sample
(:require
[ring.adapter.jetty :refer [run-jetty]]))

(defn handler [request]
{:status 200
:headers {"Content-Type" "text/plain"}
:body "Hello world"})

(defn run-dev-server
[port]
(run-jetty handler {:port port}))

Inspect your boot task

boot dev -h

Run the server

boot dev

Visit http://localhost:3000

Next, we can add hot reloading. Check that the dependencies in build.boot include ring/ring-devel. Wrap the handler into a wrap-reload handler.

(ns sample
(:require
[ring.adapter.jetty :refer [run-jetty]]
[ring.middleware.reload :refer [wrap-reload]]))

(defn handler [request]
{:status 200
:headers {"Content-Type" "text/plain"}
:body "Hello world"})

(def dev-handler
(wrap-reload #'handler))

(defn run-dev-server
[port]
(run-jetty dev-handler {:port port}))

Restart the server and visit http://localhost:3000

Now, the server will automatically reload any modified files in your source directory.