Hello, I'm trying out reittit and I'm a bit confused. Is it necessary to use it with ring? Can you define your routes using just reitit.core/router and then serve them with an httpkit server? I'm not against ring but I'm trying to test thing one step at a time.
ring in reitit documentation refers to ring application, not ring web server. You can use any as long as it is compatible with ring application. httpkit is compatible.
Thank you @delaguardo. So, to understand. Is there any server that would support running an app made of just a reitit router? What is the purpose of the standalone core router (as in not wrapped as a ring router) in real life?
I'm not aware of such server. But router itself is useful not only in the context of serving http responses. For example to handle messages in a queue, mimic defmulti with enriched context, etc.
I am trying to read either query or body parameters for a few hours now and I'm at a loss as to what I'm doing wrong. Is there anything obviously wrong with this?
(ns xyz.core
(:require [org.httpkit.server :as s]
[reitit.ring :as ring]
[reitit.ring.coercion :as rrc]
[reitit.coercion.schema]
[schema.core :as schema])
(:gen-class))
(defn hello-ctrl [req]
(let [name (-> req :parameters :path :name)
age (-> req :parameters :query :age)]
(println req)
{:status 200
:headers {"Content-Type" "text/html"}
:body (str "Hello " name " :)\nAre you " age " years old?")}))
(defn default-handler [_]
{:status 200
:headers {"Content-Type" "text/html"}
:body "It's OK, yes!"})
(def app2
(ring/ring-handler
(ring/router
[["/" {:get default-handler}]
["/hello/:name" {:name ::yolo
:get {:handler hello-ctrl
:parameters {:path {:name schema/Str}
:query {:age schema/Int}}}}]]
{:data {:coercion reitit.coercion.schema/coercion
:middleware [rrc/coerce-request-middleware]}})))
Stack trace in threadI actually figured out how to use muuntaja eventually to parse the body and how one needs to enable it etc.
👍 🎉
The stack trace
; Tue Jul 08 19:32:46 BST 2025 [http-kit-server-worker-4] ERROR - GET /hello/nick
; clojure.lang.ExceptionInfo: Request coercion failed {:schema {:age Int, Keyword Any}, :errors (not (map? [nil])), :type :reitit.coercion/request-coercion, :coercion #Coercion{:name :schema}, :value nil, :in [:request :query-params], :request {:reitit.core/match #reitit.core.Match{:template "/hello/:name", :data {:coercion #Coercion{:name :schema}, :middleware [{:name :reitit.ring.coercion/coerce-request, :spec :reitit.spec/parameters, :compile #function[reitit.ring.coercion/fn--12869]}], :name :xyz.core/yolo, :get {:handler #function[xyz.core/hello-ctrl], :parameters {:path [{:name java.lang.String}], :query [{:age Int}]}}}, :result #reitit.ring.Methods{:get #reitit.ring.Endpoint{:data {:coercion #Coercion{:name :schema}, :middleware [{:name :reitit.ring.coercion/coerce-request, :spec :reitit.spec/parameters, :compile #function[reitit.ring.coercion/fn--12869]}], :name :xyz.core/yolo, :handler #function[xyz.core/hello-ctrl], :parameters {:path {:name java.lang.String}, :query {:age Int}}}, :handler #function[reitit.ring.coercion/fn--12869/fn--12871/fn--12872], :path "/hello/:name", :method :get, :middleware [#reitit.middleware.Middleware{:name :reitit.ring.coercion/coerce-request, :wrap #function[reitit.ring.coercion/fn--12869/fn--12871], :spec :reitit.spec/parameters}]}, :head nil, :post nil, :put nil, :delete nil, :connect nil, :options #reitit.ring.Endpoint{:data {:coercion #Coercion{:name :schema}, :middleware [{:name :reitit.ring.coercion/coerce-request, :spec :reitit.spec/parameters, :compile #function[reitit.ring.coercion/fn--12869]}], :name :xyz.core/yolo, :no-doc true, :handler #function[reitit.ring/fn--12600/fn--12609]}, :handler #function[reitit.ring/fn--12600/fn--12609], :path "/hello/:name", :method :options, :middleware [#reitit.middleware.Middleware{:name :reitit.ring.coercion/coerce-request, :wrap nil, :spec :reitit.spec/parameters}]}, :trace nil, :patch nil}, :path-params {:name "nick"}, :path "/hello/nick"}, :reitit.core/router #object[reitit.core$mixed_router$reify__11754 0x70dbf017 "reitit.core$mixed_router$reify__11754@70dbf017"], :remote-addr "0:0:0:0:0:0:0:1", :start-time 932866731487291, :headers {"accept" "*/*", "host" "localhost:9393", "user-agent" "curl/8.7.1"}, :async-channel #object[org.httpkit.server.AsyncChannel 0x74e46ad4 "/[0:0:0:0:0:0:0:1]:9393<->/[0:0:0:0:0:0:0:1]:57012"], :server-port 9393, :content-length 0, :websocket? false, :content-type nil, :character-encoding "utf8", :uri "/hello/nick", :server-name "localhost", :query-string "age=37", :path-params {:name "nick"}, :body nil, :scheme :http, :request-method :get}}
; at reitit.coercion$request_coercion_failed_BANG_.invokeStatic(coercion.cljc:51)
; at reitit.coercion$request_coercion_failed_BANG_.invoke(coercion.cljc:49)
; at reitit.coercion$request_coercer$fn__12142.invoke(coercion.cljc:105)
; at reitit.coercion$coerce_request$fn__12182.invoke(coercion.cljc:142)
; at clojure.lang.PersistentArrayMap.kvreduce(PersistentArrayMap.java:475)
; at clojure.core$fn__8559.invokeStatic(core.clj:6987)
; at clojure.core$fn__8559.invoke(core.clj:6967)
; at clojure.core.protocols$fn__8287$G__8282__8296.invoke(protocols.clj:174)
; at clojure.core$reduce_kv.invokeStatic(core.clj:6998)
; at clojure.core$reduce_kv.invoke(core.clj:6989)
; at reitit.coercion$coerce_request.invokeStatic(coercion.cljc:140)
; at reitit.coercion$coerce_request.invoke(coercion.cljc:139)
; at reitit.ring.coercion$fn__12869$fn__12871$fn__12872.invoke(coercion.cljc:40)
; at reitit.ring$ring_handler$fn__12708.invoke(ring.cljc:386)
; at
I really don't know how to read this Request coercion failed {:schema {:age Int, Keyword Any}, :errors (not (map? [nil]))
My call is curl -v GET "" > I really don't know how to read this
Just to answer this part, the error is saying it found the value [nil] where it expected a keyword map.
Right, I've even copy pasted the example from here https://cljdoc.org/d/metosin/reitit/0.9.1/doc/ring/pluggable-coercion and it works if called programmatically but trying to use curl or httpie it fails with the above exception. My curl is
curl -v POST -H "Content-type: text/plain" -d "y=6" ""
or httpie
http POST "" y=5
I've tried to include ring wrap-params as my first middleware but no difference.Which example did you use, as there are several on the page.
The one under Full Example
(require '[reitit.ring.coercion :as rrc])
(require '[reitit.coercion.schema])
(require '[reitit.ring :as ring])
(require '[schema.core :as s])
(def PositiveInt (s/constrained s/Int pos? 'PositiveInt))
(def app
(ring/ring-handler
(ring/router
["/api"
["/ping" {:name ::ping
:get (fn [_]
{:status 200
:body "pong"})}]
["/plus/:z" {:name ::plus
:post {:coercion reitit.coercion.schema/coercion
:parameters {:query {:x s/Int}
:body {:y s/Int}
:path {:z s/Int}}
:responses {200 {:body {:total PositiveInt}}}
:handler (fn [{:keys [parameters]}]
(let [total (+ (-> parameters :query :x)
(-> parameters :body :y)
(-> parameters :path :z))]
{:status 200
:body {:total total}}))}}]]
{:data {:middleware [rrc/coerce-exceptions-middleware
rrc/coerce-request-middleware
rrc/coerce-response-middleware]}})))I've tried modifying my curl Content-type and got to see the body value as a param using the middleware logger but still I have not managed to use the :body as whatever I do this key is not generated.
I can see y as a form param though using the default "application/x-www-form-urlencoded"
; --- :request :reitit.ring.middleware.parameters/parameters ---
;
; {:async-channel #<org.httpkit.server.AsyncChannel@64934162 /[0:0:0:0:0:0:0:1]:9393<->/[0:0:0:0:0:0:0:1]:60487>,
; :body #<org.httpkit.BytesInputStream@64e1ea05 BytesInputStream[len=3]>,
; :character-encoding "utf8",
; :content-length 3,
; :content-type "application/x-www-form-urlencoded",
; :headers {"accept" "*/*",
; "content-length" "3",
; "content-type" "application/x-www-form-urlencoded",
; "host" "localhost:9393",
; "user-agent" "curl/8.7.1"},
; :path-params {:z "2"},
; :query-string "x=34",
; :remote-addr "0:0:0:0:0:0:0:1",
; :request-method :post,
; :scheme :http,
; :server-name "localhost",
; :server-port 9393,
; :start-time 946790008908541,
; :uri "/api/plus/2",
; :websocket? false,
; +:form-params {"y" "6"},
; +:params {"x" "34", "y" "6"},
; +:query-params {"x" "34"}}Here is a working version:
(ns xyz.core
(:require
[muuntaja.core :as m]
[reitit.coercion.schema]
[reitit.ring :as ring]
[reitit.ring.coercion :as rrc]
[ring.adapter.jetty9 :as jetty]
[ring.middleware.params :as params]
[schema.core :as schema])
(:gen-class))
(defn hello-ctrl [req]
(let [name (-> req :parameters :path :name)
age (-> req :parameters :query :age)]
(println req)
{:status 200
:headers {"Content-Type" "text/html"}
:body (str "Hello " name " :)\nAre you " age " years old?")}))
(defn default-handler [_]
{:status 200
:headers {"Content-Type" "text/html"}
:body "It's OK, yes!"})
(defn app2
[]
(ring/ring-handler
(ring/router
[["/" {:get default-handler}]
["/hello/:name" {:name ::yolo
:get {:handler hello-ctrl
:parameters {:path {:name schema/Str}
:query {:age schema/Int}}}}]]
{:data {:muuntaja m/instance
:coercion reitit.coercion.schema/coercion
:middleware [params/wrap-params
rrc/coerce-request-middleware]}})))
(defn go
[]
(jetty/run-jetty (fn [request] ((app2) request)) {:port 3001 :allow-null-path-info true :join? false}))❯ curl \?age\=30
Hello nick :)
Are you 30 years old?% A few things:
You need ring to decode the params, that's what the params middleware is about
(however, you could, instead use this: [reitit.ring.middleware.parameters :as parameters])
(I use that, which just wraps rings params)
:middleware [
....
muuntaja/format-middleware
parameters/parameters-middleware
....]
...
...
...Secondly, for repl usage, I wrap app2 into a function, so I can re-eval the form and not have to start/restart the server.
.
.
Hope that helps!
Have a look here:
.
Right, in between copy-pasta I forgot to post my latest version which was using
{:data {:middleware [reitit.ring.middleware.parameters/parameters-middleware
ring.middleware.keyword-params/wrap-keyword-params
rrc/coerce-request-middleware
rrc/coerce-response-middleware
rrc/coerce-exceptions-middleware
]}
but I've missed the wrap-params . For reloading I use the wrap-reload middleware in a dev namespace where I launch the server. I'll need to update my code and test. Thank you!you're most welcome! 🙂 Good luck! 🙂
.
Sorry @dharrigan but is what you mean above that I could use
reitit.ring.middleware.parameters :as parameters
in place of
ring.middleware.params/wrap-params
?
As, I did that, reading the reitit docs and how the first wraps the second and it did not works for me. Now using the second one I can see the body getting parsed but I still get a complain and I'll need to figure out what is the issue.For some reason I can't get Ring + Reitit to parse POST requests as Json. Does something look wrong with this?
(def routes
(reitit.ring/ring-handler
(reitit.ring/router
[["/" {:get home-page}]
["/authenticate/login" {:post auth/login-route
:data {:middleware [wrap-json-body]}}]]
{:data {:middleware [format-middleware]
:muuntaja muuntaja/instance}})
(ring/create-default-handler)))does the request have content-type: application/json; charset=utf-8 header?