beginners

ChillPillzKillzBillz 2025-09-02T16:28:44.153349Z

Hi All, I've been trying to create a websocket server. I am new to this... but firstly there don't seem to be enough good examples which show exactly how to setup a websocket and secondly they seem to use a combination of libraries. Is there some monolithic library and an accompanying example which will show me how to create a websocket server? Many thanks for your help!

p-himik 2025-09-02T16:31:19.353029Z

If you're looking for a server+client, then maybe Sente is the way to go. If you're looking at just the server, then maybe https://github.com/sunng87/ring-jetty9-adapter.

2025-09-02T16:46:30.512129Z

@abhishek.mazy I think the simplest would be to go with http-kit, which handles websockets already. Try this :

$ clj -Sdeps '{:deps {http-kit/http-kit {:mvn/version "2.8.1"}}}'

(require '[org.httpkit.server :as http])

(defn app [req]
  (case (:uri req)
    "/ws" (http/as-channel
           req
           {:on-receive (fn [ch message]     (http/send! ch "Echoing back: " message))
            :on-close   (fn [ch status-code] (println "Closed with code:" status-code))
            :on-open    (fn [ch] (println "Got a connection!" ch))})

    {:status 404
     :body "Page not found"}))

(http/run-server app {:port 8080})

2025-09-02T16:46:54.716189Z

you should be able to connect to <ws://localhost:8080/ws>

2025-09-02T16:47:12.479819Z

you can quickly try it using something like https://piehost.com/websocket-tester

2025-09-02T16:47:16.579419Z

How about this example? https://github.com/ring-clojure/ring-examples/tree/master/websocket-chat

ChillPillzKillzBillz 2025-09-02T16:47:28.759019Z

@jpmonettas I'll give it a go

p-himik 2025-09-02T16:48:26.437839Z

According to the Jetty adapter I've linked, it's pretty much just as easy:

(require '[ring.adapter.jetty9 :as jetty])

(def ws-handler {:on-open (fn [ws])
                 :on-error (fn [ws e])
                 :on-close (fn [ws status-code reason])
                 :on-message (fn [ws message])
                 :on-ping (fn [ws bytebuffer])
                 :on-pong (fn [ws bytebuffer])})

(defn app [req]
  (if (jetty/ws-upgrade-request? req)
    (jetty/ws-upgrade-response ws-handler)))

(run-jetty app)
With the added benefit of Jetty being a kind of a universal constant that reliably works with anything and everything.

ChillPillzKillzBillz 2025-09-02T16:50:34.497599Z

I'll try all these different approaches out. Many thanks all. This helps!

ChillPillzKillzBillz 2025-09-02T16:55:27.565819Z

@p-himik I can't seem to find the jetty/ws-upgrade-response in the library

ChillPillzKillzBillz 2025-09-02T16:55:39.823149Z

do you know where to find a response?

p-himik 2025-09-02T17:00:18.454479Z

Hmm, it seems that the author has changed how the underlying functionality is supposed to work but forgot to update the README. Can you try this instead? https://github.com/sunng87/ring-jetty9-adapter/blob/master/examples/rj9a/websocket.clj

ChillPillzKillzBillz 2025-09-02T19:45:04.333109Z

@p-himik when I try the code from https://github.com/sunng87/ring-jetty9-adapter/blob/master/examples/rj9a/websocket.clj I cant get past the require lines as I get the following error

; Execution error (ClassNotFoundException) at java.net.URLClassLoader/findClass (URLClassLoader.java:445).
; org.eclipse.jetty.server.handler.HandlerCollection
My deps.edn includes the following libraries
org.ring-clojure/ring-core-protocols {:mvn/version "1.14.2"}
        org.ring-clojure/ring-websocket-protocols {:mvn/version "1.14.2"}
        info.sunng/ring-jetty9-adapter {:mvn/version "0.7.0"}
        org.eclipse.jetty/jetty-server {:mvn/version "12.1.0"}
        org.eclipse.jetty/jetty-util {:mvn/version "12.1.0"}
        org.eclipse.jetty.websocket/jetty-websocket-jetty-api {:mvn/version "12.1.0"}
        org.eclipse.jetty.websocket/jetty-websocket-jetty-server {:mvn/version "12.1.0"}
I am not sure what I am doing wrong. This was the original problem that I faced which prompted the original question.

p-himik 2025-09-02T19:51:24.158839Z

I just tried it. Copied it verbatim, added just two dependencies:

info.sunng/ring-jetty9-adapter       {:mvn/version "0.37.6"}
ring/ring-core                       {:mvn/version "1.14.2"}
And it worked like a charm without any changes.

ChillPillzKillzBillz 2025-09-02T19:51:50.906849Z

ok I'll try with your suggestions and let you know

ChillPillzKillzBillz 2025-09-02T19:51:53.766949Z

thanks again!

p-himik 2025-09-02T19:52:26.889509Z

No problem.

rikkarth 2025-09-02T23:12:48.891369Z

Does anyone get weird lints with clj-kondo?

(ns learning.LearnExample)

(defstruct car :type :price)
This block produces Unresolved symbol: car (clj-kondo) in car. Why?

borkdude 2025-09-07T15:33:18.944259Z

I now have a PR up for this which I will merge shortly. Turns out clojure itself still uses defstruct in clojure.pprint :) https://github.com/clj-kondo/clj-kondo/pull/2620/files

💯 1
2025-09-02T23:13:37.756579Z

i don't think clj-kondo supports defstruct

2025-09-02T23:17:41.149619Z

in general, you want to use records, not structs

2025-09-02T23:19:18.611899Z

Ya, structs are not used since a long time, as defrecord fully superset it. Of course, in Clojure nothing is ever removed, and they still work as promised. But I guess clj-kondo pretends like they don't exist.

rikkarth 2025-09-02T23:20:00.482999Z

I just switched to record and clj-kondo works fine. Thank you 🙂 I guess the only way to enforce types in a record is using metosin/malli ?

2025-09-02T23:20:01.747809Z

@borkdude Is this true?

2025-09-02T23:20:46.776859Z

Records create a real Java type of the same name.

2025-09-02T23:21:29.906789Z

I guess it depends what you mean by "enforce"? But a Car record is of type Car

rikkarth 2025-09-02T23:23:00.129829Z

I mean if I have a:

(defrecord Car [type price])
How do I make sure price is of type int, float, double or at the very least "number"?

rikkarth 2025-09-02T23:23:38.862909Z

I'm placing this question in a weird way, but I'd like to have some sort of soft-type checking at least.

rikkarth 2025-09-02T23:24:02.159009Z

Since I can literally pass anything right now to price and Clojure will be like "sure"

2025-09-02T23:24:32.099529Z

you do that at creation time with any number of validation techniques or libraries

2025-09-02T23:25:05.710459Z

(defn make-car [price] (assert (int? price)) (->Car price))

2025-09-02T23:25:34.651059Z

(on my phone or i'd be more elaborate)

rikkarth 2025-09-02T23:25:54.032539Z

No worries I totally get it thank you Noah.

rikkarth 2025-09-02T23:25:57.628449Z

I see, so is this something common in Clojure? Sorry for the weird question but I'm coming from plain Java.

rikkarth 2025-09-02T23:26:23.647619Z

I don't mind validating input types if this is Clojure's design principle.

2025-09-02T23:28:10.130179Z

Ya, there's no baked in type validation, neither runtime nor statically. What people normally do is this:

(defn make-car [type price]
  (validate ...)
  (->Car type price))
How the "validate" happens is up to you. You can use Malli, Spec, you can manually do it, etc.

👍 2
rikkarth 2025-09-02T23:30:51.956369Z

I'm sure most responses will vary with this question, but I'm looking for an Industry standard (if there's one). What would you say is most used? Malli, Spec or manual validation? I'm sure there's always some level of manual validation even when using a library.

2025-09-02T23:33:25.411019Z

I'd say it's most common to just embrace the unknown of types 😛, and not validate. But for like the core entities of an app, I think it's common to have a form of spec (malli, spec, or other spec-like definition) and validate on creation and update to make sure that it maintains the invariants. These would often start to even embed business logic in them, like price needs to be between 1000$ and 1000000$ for example.

2025-09-02T23:36:14.612999Z

Plumatic Schema used to be the most popular lib for this, then it turned to Spec, and now Malli is gaining ground. There's probably a good mix of people still using all 3.

rikkarth 2025-09-02T23:37:19.556059Z

> I'd say it's most common to just embrace the unknown of types 😛, and not validate.

😂 1
rikkarth 2025-09-02T23:39:27.554739Z

I feel super privileged here because while I programmed in a super statically typed language, I also work with the complete opposite everyday Python, so Clojure doesn't feel super weird to me in that front. I've learned to embrace the chaos, so I totally understand what you mean xD But at the same time, for the sake of good composition I like to see the types of each model property.

rikkarth 2025-09-02T23:39:44.932909Z

I guess, from what I just saw in Malli this aligns with what I'm looking for.

rikkarth 2025-09-02T23:40:12.487599Z

I don't want to go super hardcore on having everything typed, I want to embrace the Clojure paradigm.

rikkarth 2025-09-02T23:40:32.299809Z

Thank you 🙂 Your words do make me feel a bit more relaxed ^_^

2025-09-02T23:42:17.615379Z

Ya, so I think what's happening is you are trying to find what to use in Clojure to represent your "data model". And in Java, a class also acts as your schemas for your main entities and such. Definitely for that in Clojure the "official" thing would be spec. But it works differently, you basically define schemas, as specs. But they don't automagically tie themselves to a custom data structure that you can instantiate.

seancorfield 2025-09-02T23:42:37.192779Z

Forget types. They over-prescribe things.

💯 1
seancorfield 2025-09-02T23:43:02.180309Z

Use maps instead of records.

seancorfield 2025-09-02T23:43:20.455579Z

Unless you really need the underlying Java type for dispatch.

rikkarth 2025-09-02T23:43:54.897959Z

> Unless you really need the underlying Java type for dispatch. I really don't.

2025-09-02T23:44:03.274339Z

But ya, I'd say start without specs as well. See how far the most simple thing takes you (don't even use records). You want to model a Car?

(ns my-app.car)

(defn make-car
  [type price]
  {:type type
   :price price})

1
rikkarth 2025-09-02T23:46:24.235599Z

yeah maybe i'm just really overcomplicating it

rikkarth 2025-09-02T23:46:33.154249Z

this looks a lot more digestable

2025-09-02T23:47:02.956219Z

And to be honest, people often don't bother having one-namespace per entity like that. Cause Clojure people, we like concise haha. We don't like having a million LOC, a million files. So you'll often see:

(ns my-app.entities)

;;;; Car

(defn make-car
  [type price]
  {:type type
   :price price})
 
;;;; Truck

(defn make-truck
  [type price num-of-wheels]
  (assoc (make-car type price) :num-of-wheels num-of-wheels))

1
rikkarth 2025-09-02T23:49:59.484939Z

i'm definitely following this, thank you, this is the kind of feedback and learning i've been looking for. i really appreciate your help 🙏

rikkarth 2025-09-02T23:50:22.938529Z

it makes a lot more sense seeing your examples

rikkarth 2025-09-02T23:51:21.615509Z

i see myself for sure still holding onto oop a lot

rikkarth 2025-09-02T23:51:32.731839Z

in this sense

seancorfield 2025-09-02T23:55:32.536059Z

Giving up types can be hard. They feel like a safety net, but they really can be a straitjacket 🙁 Same with OO - we're so used to it in the mainstream but FP is even longer-lived and works really well (and is simpler).

🤝 1
Ludger Solbach 2025-09-03T02:45:57.944359Z

A lot simpler, most of the time IMHO

borkdude 2025-09-03T06:18:16.403719Z

Clj-kondo doesnt support defstruct but since it has come up before I might implement it. Issue welcome

💯 1
borkdude 2025-09-05T10:45:26.216379Z

I re-opened this issue https://github.com/clj-kondo/clj-kondo/issues/1894