After learning about setting up a Clojure project with deps.edn, I wanted to build a simple web server. I found a video, linked below, and worked out a simple example. Next, I’d like to build a ClojureScript frontend.

Initial Setup

First we’ll set up our directory and write a Hello World application.

brew install clojure 
mkdir simple-web-server
cd simple-web-server
touch deps.edn

deps.edn

Then we can write our dependencies file in deps.edn,

{
  :paths ["src/clj"]
  :deps {
            org.clojure/clojure {:mvn/version "1.12.1"}
            http-kit/http-kit {:mvn/version "2.8.0"}
            compojure/compojure {:mvn/version "1.7.1"}
            cheshire/cheshire {:mvn/version "5.10.0"}}
  :aliases {
    :run {:main-opts ["-m" "simple-web-server.core"]}
    :repl {:extra-deps {nrepl/nrepl {:mvn/version "1.3.1"}}}       
  }
} 

Simpler Server

Then we can write a lightweight server,

mkdir -p src/clj/simple_web_server
cd src/clj/simple_web_server
touch core.clj

Here, the context function allows us to next routes. In the second json route, we’re using a keyword :id in the route, which is passed from the request, and into the function, as an argument.

(ns simple-web-server.core
  (:require
   [compojure.core :refer [defroutes context GET POST]]
   [compojure.route :as route]
   [cheshire.core :as json]
   [org.httpkit.server :refer [run-server]]))

(defroutes app
  (GET "/html" [] "<h1>HELLO HTML ENDPOINT</h1>")
  (context "/json" []
    (GET "/" [] {:status 200
                     :headers {"Content-Type" "application/json"}
                     :body (json/generate-string {:message "Hello JSON endpoint"})}) 
    (GET "/:id" [id] {:status 200
                 :headers {"Content-Type" "application/json"}
                 :body (json/encode {:message "Hello JSON endpoint" :id id})})
    (POST "/" request
      (let [body (json/parse-string (slurp (:body request)) true)]
        {:status 200
         :headers {"Content-Type" "application/json"}
         :body (json/encode {:message "Received JSON data" :data body})})))
    )

  (route/not-found {:status 404
                    :headers {"Content-Type" "application/json"}
                    :body (json/encode {:error "Not Found"})}))

(defn start-server []
  (println "Starting server on port 4321...")
  (run-server app {:port 4321}))

(defn -main []
  (start-server))

(defonce server (atom nil))

(defn start []
  (reset!  server (start-server)))

(defn stop []
  (when @server
    (@server :timeout 100)))

(defn restart []
  (stop)
  (start))

Then run from the root simple-web-server/ directory as,

clj -M:run

Testing the Endpoints

And now we can test this by using httpie. Here is the GET request:

$ http localhost:4321/json/1
HTTP/1.1 200 OK
Content-Type: application/json
Date: Sun, 6 Jul 2025 02:22:37 GMT
Server: http-kit
content-length: 42

{
    "id": "1",
    "message": "Hello JSON endpoint"
}

Here is a POST request:

$ http POST localhost:4321/json/ foo=bar   
HTTP/1.1 200 OK
Content-Type: application/json
Date: Sun, 6 Jul 2025 02:34:32 GMT
Server: http-kit
content-length: 53

{
    "data": {
        "foo": "bar"
    },
    "message": "Received JSON data"
}

References

This is adapted and update from an older YouTube video by Kelvin Mai.