The Problem
I started playing with pedestal to create Clojure micro services. Coming from the Nodejs world, and Clojure being a REPL driven development language, I naturally expected to have watch for code changes and reload without having to restart the server. I searched around and found solutions, but I couldn't get it to work trivially. This guide is meant to serve as a step-by-step guide to achieve this
Getting Started
Create a new pedestal service project
lein new pedestal-service my-service
Now, run the server
lein run
Go to http://localhost:8080 (default port) and you will see "Hello World!"
REPL-driven Development
Now that you have successfully seen the service working, lets jump into REPL-driven development
Kill the running server
and then start a nREPL using
lein repl
Once the REPL starts, connect your IDE (I used Calva on Visual Studio Code) to the REPL (refer here for a detailed Calva setup guide)
Once the REPL starts, you can call the run-dev function to start the server in dev mode
(def dev-server (run-dev))
Now head on to http://localhost:8080 to see the browser print "Hello World!" again.
Now open service.clj and try to modify the handler function for the '/' route home-page to return a different message
(defn home-page
[request]
(ring-resp/response "Modified message!"))
Try to reload the browser and you will still see "Hello World!". Lets fix this!
Enter ns-tracker
This is the initial service function in service.clj
;; Tabular routes
(def routes #{["/" :get (conj common-interceptors `home-page)]
["/about" :get (conj common-interceptors `about-page)]})
(def service {:env :prod
::http/routes routes
::http/resource-path "/public"
::http/type :jetty
::http/port 8080
::http/container-options {:h2c? true
:h2? false
:ssl? false}})
Lets modify the routes var to a function that returns the same route
(defn routes []
#{["/" :get (conj common-interceptors `home-page)]
["/about" :get (conj common-interceptors `about-page)]})
Now, open server.clj where the run-dev function is defined. This is the correct place to track for source changes and reload.
ns-tracker library provides a function that tracks one or more source directories for file changes that can be used to reload the modified namespaces on the fly. However, this does not work standalone. You need to wrap the routes map of the service around a function and associate the function to the ::http/routes entry in the service map
Require ns-tracker in server.clj
(:require [ns-tracker.core :refer [ns-tracker]])
Define the source directories to be tracked by ns-tracker
(defonce modified-namespaces
(ns-tracker ["src" "test"]))
Define a watch-routes-fn
(defn watch-routes-fn [routes]
(fn []
(doseq [ns-sym (modified-namespaces)]
(require ns-sym :reload))
(route/expand-routes routes)))
The watch-routes-fn returns a function that closes in the routes and calls route/expand-routes on it when invoked
Finally, we wrap the ::server/routes entry in the service config map with the watch-routes-fn function call like
(defn run-dev
"The entry-point for 'lein run-dev'"
[& args]
(println "\nCreating your [DEV] server...")
(-> service/service ;; start with production configuration
(merge {:env :dev
;; do not block thread that starts web server
::server/join? false
;; Routes can be a function that resolve routes,
;; we can use this to set the routes to be reloadable
::server/routes (watch-routes-fn (service/routes))
;; all origins are allowed in dev mode
::server/allowed-origins {:creds true :allowed-origins (constantly true)}
;; Content Security Policy (CSP) is mostly turned off in dev mode
::server/secure-headers {:content-security-policy-settings {:object-src "'none'"}}})
;; Wire up interceptor chains
server/default-interceptors
server/dev-interceptors
server/create-server
server/start))
See it working!
Restart the nREPL and connect your IDE to it. Now, call
(def dev-server (run-dev))
Modify the handler to return a different string and head on to the browser to test it. You should now see the modified message show up!
Top comments (0)