Watch and auto reload a Clojure pedestal service on source change
Tags: clojure, pedestal
Published: 2022-03-13 08:20:22

## The Problem
I started playing with [pedestal](http://pedestal.io) to create Clojure micro services. Coming from the Nodejs world, and [Clojure](https://clojure.org/) 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](https://calva.io/getting-started/) for a detailed Calva setup guide)

Once the REPL starts, you can call the run-dev function to start the server in dev mode

```clojure
(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

```clojure
(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

```clojure
;; 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

```clojure
(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

```clojure
(:require [ns-tracker.core :refer [ns-tracker]])
```

Define the source directories to be tracked by ns-tracker

```clojure
(defonce modified-namespaces
  (ns-tracker ["src" "test"]))
```

Define a watch-routes-fn 

```clojure
(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 

```clojure
(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 

```clojure
(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!