Kit: Accessing Request Body Fields in POST Request

I’d like to write a Kit web application (REST API) that handles JSON in POST endpoints. (EDIT: It is available in this Git repo: GitHub - patrickbucher/kit-example)

I created a new Kit application:

clj -Tclj-new create :template io.github.kit-clj :name com.github.patrickbucher/kit-example

In src/clj/com/github/patrickbucher/kit_example/web/controllers/health.clj, I added another handler function:

(defn echo [req]
  (:body req))

Which I route in src/clj/com/github/patrickbucher/kit_example/web/routes/api.clj:

(defn api-routes [_opts]
  [["/swagger.json"
    {:get {:no-doc  true
           :swagger {:info {:title "com.github.patrickbucher.kit-example API"}}
           :handler (swagger/create-swagger-handler)}}]
   ["/health" {:get #'health/healthcheck!}]
   ["/echo" #'health/echo]]) ;; <<-- this is my code

Now I start the application:

$ clj -M:dev:nrepl
user=> (go)

The healthcheck works:

curl -X GET localhost:3000/api/health

Result:

{"time":"Mon Apr 21 08:13:49 CEST 2025","up-since":"Mon Apr 21 08:13:10 CEST 2025","app":{"status":"up","message":""}

But my echo endpoint fails:

curl -X POST localhost:3000/api/echo -H 'Accept: application/json' -H 'Content-Type: application/json' -d '{"foo": "bar"}'

Error Message:

2025-04-21 08:15:10,902 [XNIO-2 task-2] ERROR io.undertow.request - UT005071: Undertow request failed HttpServerExchange{ POST /api/echo} 
java.lang.IllegalArgumentException: contains? not supported on type: io.undertow.io.UndertowInputStream

So I’m dealing with an UntertowInputStream, not with a map based on the deserialized JSON input.

I alter the echo endpoint as follows for debugging:

(defn echo [req]
  (println (slurp (:body req)))
  {:foo "bar"})

After which I get the following exception:

2025-04-21 08:20:05,022 [XNIO-3 task-2] ERROR c.g.p.k.web.middleware.exception - #error {
 :cause UT000034: Stream is closed
 :via
 [{:type java.io.IOException
   :message UT000034: Stream is closed
   :at [io.undertow.io.UndertowInputStream read UndertowInputStream.java 110]}

Since there must be a conceptual misunderstanding, I spare you the whole stack trace.

In src/clj/com/github/patrickbucher/kit_example/web/middleware/formats.clj, the following is defined (I didn’t change it):

(ns com.github.patrickbucher.kit-example.web.middleware.formats
  (:require
    [luminus-transit.time :as time]
    [muuntaja.core :as m]))

(def instance
  (m/create
    (-> m/default-options
        (update-in
          [:formats "application/transit+json" :decoder-opts]
          (partial merge time/time-deserialization-handlers))
        (update-in
          [:formats "application/transit+json" :encoder-opts]
          (partial merge time/time-serialization-handlers)))))

How can I request the request body as a Clojure data structure?

Nevermind, it’s (:body-params req) instead of (:body req). :man_facepalming: