Fork me on GitHub

Good day, TLDR: What's the 20% of Clojure needed to build 80% of CRUD apps? I'm a novice programmer interested in web dev, basic sites with an index page, show pages, item filtering, search, auth, and a db. (zero plans for a career in development). I know basic HTML, CSS, JS, and SQL with a slightly deeper understanding of Elixir and Phoenix. I'm drawn to the simplicity of Clojure as an escape from frameworks, arbitrary syntax and OOP. Clojure wise, I've done a short udemy course and a few other small tutorials. For my main learning resource I've settled on ( I found it to be the most practical and up to date Clojure book, that doesn't teach frameworks. But at 700+ pages, with everything from hello world to macros it feels comprehensive to a fault. My naive intuition is that there's a subset of Clojure concepts that if learned, would give me out-sized returns compared to the rest. I'm including a list of the books contents and I'd be grateful for any feedback on which chapters/concepts could be strategically ignored from the POV of an aspiring Clojure CRUD maker? 1. Hello REPL! (def, let, booleans, repl basics) 2. Data Types and Immutability (maps, sets, vectors, lists) 3. Functions in Depth (destructing, higher-order functions, multi-methods) 4. Mapping and Filtering (map, filter, lazy evaluation) 5. Many to One: Reducing (zipmap, group by, reduce) 6. Recursion and Looping (loop, doseq, recur, take, repeatedly, iterate) 7. Recursion II: Lazy Sequences (lazy-seq, thunks) 8. Namespaces Libraries and Leiningen 9. Host Platform Interoperability with Java and JavaScript (exceptions, errors) 10. Testing 11. Macros 12. Concurrency (pmap, futures, atoms, refs, agents) 13. Database Interaction and the Application Layer (JDBC, SQL, Connection pools) 14. HTTP with Ring 15. The Frontend: A ClojureScript UI (reagent, HTTP endpoints) *cross posted on clojureverse


First off, I'll say that, unfortunately, Packt books are generally terrible. They're poorly edited, often contain a lot of bad code and mistakes, and can be pretty outdated.

👍 1

"Getting Clojure" is heavily recommended here for beginners. "Programming Clojure" is also a good book to get you up and running with Clojure. And then, as a follow-on book to either of those, "Clojure Applied" (but don't get too hooked on the emphasis it gives to records -- that's something the author would change for a second edition).

👍 1

For building simple web apps, backed by a database, I'd recommend either: • Ring, Compojure, Component, Selmer, next.jdbc, or • Ring, reitit, Integrant, Selmer, next.jdbc. There's an example of the former at and it links to an example of the latter, created from the former, by the community.


All that said, to your original question, I'd say for what you are aiming at -- simple, database-backed web apps -- you would skip 7, parts of 9, 11, 12, 15. I'd recommend learning the Clojure CLI / deps.edn / instead of Leiningen (partly because they're are a set of small, composable tools -- so they have a more modern design -- and they're all from the core Clojure team so they're documented on The usermanager example is deps.edn-based, but doesn't have an example of for creating a standalone JAR to run with java -jar... yet...

👍 1

(it's updated to include build.clj and show how to use to run tests and build an uberjar)

👍 1

LMK if you have any Qs about the usermanager example app @U01HBARPCFL

👍 1

I would disagree about packet, there are some really good books (but a few bad ones too -it mostly depends on the author) The Clojure Workshop book is a good general Clojure book and has accompanying videos that help convey some of the concepts in action. Some libraries may be considered a little dated (disclaimer: I recorded some videos for this book but didn't write any of it)

👍 1

For a beginner learning to build a server-side web service, then ring (adaptor, handler, middleware) is the underlying concept. It translates http requests into Clojure maps. On top of ring there is routing, for which compojure is very common, with Reitit and other libraries offering a data-centric routing approach

👍 1

Hiccup provides a simple approach to generating content by writing Clojure data structures. Hiccup syntax is very common. Selmer uses an html templating approach, which can be better for larger amounts of content or for people used to working with html/css

👍 1

For relational database (SQL) next.jdbc library is the defacto library, writing queries using SQL syntax. with-open will manage dB connections cleanly and next.jdbc supports transactions dB connection pool libraries (although they don't seem to be needed in the case you define)

👍 1

If your not deploying production systems, then it seems security is not relevant (so can skip things like buddy and friend)

👍 1

From the list of chapters, I'd start with chapter 14 and only read small bits from other chapters if required next.jdbc has a very good set of docs, so maybe an alternative to chapter 13

👍 1

There are some simple project walkthroughs of server-side projects at

👍 1

For the benefit of anyone else interested in this topic that doesn't frequent clojureverse. I'll repost my revised plan here: Thanks to the great advice here and elsewhere, I've put together a new plan that I'm happy with. 1. Per @U05254DQM suggestion replace chapter 13 with the next-JDBC docs. 2. Complete chapter 14 3. Switch from Leiningen to the Clojure CLI 4. Follow @U0K064KQV "Build it and you will learn" suggestion, back-filling knowledge gaps as they arrive . 4b. Study @U04V70XH6's demo repo and others I can find.

👍 2

@U01HBARPCFL If you have any Qs about the next.jdbc docs, feel free to ask in #sql. If you have any Qs about the Clojure CLI or deps.edn, ask in #tools-deps. build.clj in #tools-build. My demo repo -- DM me. Or ask any of these Qs in #beginners if you think they're more general, rather than specific library/tool-related. Good luck!

👍 2

Thanks Sean, will do


Oh any coming in here - make sure to use the newest hiccup. 2.0.0-alpha3 or something. O.G. hiccup has injection vulnerabilities by default hiccup2 does not

👍 1
Yogesvara Das11:05:52

Like the way ? is stuck onto functions that return a boolean is there some idiom for functions that perform assertions?

🙌 1

What do you mean by assertions?


Nothing I'm aware off. Assertions are mostly used for checking invariants and I don't remember seeing any special naming convention for t if that


Jo can I decode a base16 string with interop? I mean without some lib

Alex Miller (Clojure team)14:05:14

like (Integer/parseInt "00ff00" 16) ;;=> 65280 ?


no the output should be a string. this here does that

Alex Miller (Clojure team)14:05:59

well what is the encoding then?


I somehow thought "hex" or "'base16" for some reason


it's a string like this "\\x7b2246726f6d223a22307832663437656235623238316132363939646339363865643430366362653430653638626631303634222c22546f223a22307836653437663631666631646161643738376663303963353135366163323563373266373838646435222c2256616c7565223a3731393131333133353530353432323432313637337d"

Alex Miller (Clojure team)14:05:20

it appears that is converting each pair of hex values to a character in ASCII encoding

Alex Miller (Clojure team)14:05:36

I mean that's what the page is doing, not sure if that's what you want


pretty sure it's what I want because it returns something that looks right


   (drop 2)
   (partition 2)
   (map #(apply str %))
   (map #(Integer/parseInt % 16))
   (map #(Character/toString %))
   (apply str))


Hi all, I am trying to write a cronjob and using sh however I keep getting an odd error which I can’t get my head around. Code:

(shell/with-sh-env {:PGPASSWORD (env :production-db-password)}
                     (sh "pg_dump"
                         (str "--dbname=postgresql://"
                              (env :production-db-user)
                              (env :production-db-instance)
                              "/amp-compute-products -W")
(shell/with-sh-env {:PGPASSWORD "pass"} (sh "pg_dump" (str "--dbname=postgresql://" "username" "@" "localhost:5432" "/amp-compute-products -W") "--file=production.sql" "--format=tar")) - failed: vector? at: [:fn-tail :arity-1 :params] spec: :clojure.core.specs.alpha/param-list
Could anyone possibly shine some light on what I’m doing wrong here?


it's good to always build from some small form that works ( "ls" "-a") for instance this still works

👍 1

it works in my repl, but when i run lein uberjar it doesn’t work 😕


sh - failed: vector? at: [:fn-tail :arity-n :bodies :params] spec: :clojure.core.specs.alpha/param-list
(sh "ls" "-a") - failed: vector? at: [:fn-tail :arity-1 :params] spec: :clojure.core.specs.alpha/param-list


even failed for (sh "ls" "-a")


think i found my issue - I was putting it in a function and didn’t have [] after the func name


yea sounds likely


we could probably enhance the docstring of shell/sh. it's not obvious that the commands should precede the options

Jon Olick16:05:07

is memoize thread-safe?

Jon Olick16:05:58

It seems fundamentally mutable

Jon Olick16:05:05

(with hidden state)


it is thread-safe


there are very few places of unrestrained 'mutation' in clojure. Most 'mutation' uses something called the "succession model", where you apply a function to the contents of the box, not simply bang on the box

Jon Olick16:05:49

yeah, hence my confusion 🙂


memoize makes no other guarantees (such as computing a function at most once for a distinct set of args)

Jon Olick16:05:22

I imagine delay has the same multi-threading issues

Alex Miller (Clojure team)16:05:16

delay is also thread-safe


yes. and delay guarantees that the delayed function is called at most once.


As a Clojure user, my impression was that memoize, just like swap!, require you to apply it to only pure functions. So it doesn't really matter if you compute it twice with the same args, it will cache the same result. But delay can be used with side-effects, its one of the advantage of delay, because it will execute it only once and cache the result.


I guess it plays on what is meant by thread safe, you still need to be aware of thread concurrency issues as a user in how you use the constructs. I see them more as thread safety constructs, not thread safe functions of their own, but mechanism you can use to implement some behavior in a thread safe manner.


For side effect, and memoize like caching behavior, you can use core.memoize and core.cache instead, see: and


> Naive cache (memo) that duplicates the functionality of Clojure's memoize function but, unlike the built-in memoize function, ensures that in the case of concurrent calls with the same arguments, the memoized function is only invoked once


You can see the doc for memoize says: > Returns a memoized version of a referentially transparent function. So it's meant to be used with pure functions only. The doc-string of swap! also mentions that f should be side effect free. Whereas the doc-string for delay mentions no such restrictions. The doc-strings are not always the most consistent, but if there is no restrictions mentioned on f, typically that means its fully thread safe in all cases. But if it mentions a restriction, well you have to make sure your f abide by it or know what you are doing otherwise.


I'm connecting to a basic socket repl via an ssh tunnel using this as a "client": and the following alias to start the server:

  {:exec-fn   clojure.core.server/start-server
   :exec-args {:name          "repl-server"
               :port          5555
               :accept        clojure.core.server/repl
               :server-daemon false
Randomly every few hours, my server jvm will die with no obvious error output. My current assumption that some connectivity issues with the client are causing the server to die. I'm running a more complicated app than is describe here and this issue is mostly caused by something else in the stack, just want to sanity check that nothing obvious in this basic setup should result in an issue with a client killing the server, right?


have you eliminated os/configuration issues?


sometimes servers are configured to kill unknown processes over some age


or linux has an oomkiller that kills processes when it is running out of memory


Nope, it definitely could be that, been stumped by this for a few days. It happens too infrequently and fails so silently that I haven't got very far figuring it out. Thanks for the tips that's a place to start!


Yup confirmed it was the oomkiller, must have a memory leak somewhere. Thanks!

James Amberger19:05:43

I am fascinated by transducers. I have a question. Why, since the advent of transducers, doesn’t (map f coll) get defined as (transduce (map f) conj coll) Is this just a preserve-existing-behavior kind of thing?


that would change the behavior of (map f coll):

pivot=> (type (map inc (range 10)))
pivot=> (type (transduce (map inc) conj (range 10)))
pivot=> (conj)

👆 1
James Amberger20:05:09

so lazy-ness is the issue?


yes your proposed version would make map eager rather than lazy

James Amberger20:05:00

transduce will immediately (not lazily) reduce over coll”

James Amberger20:05:29

hmm so what’s the equivalent of lazy-transduce

Alex Miller (Clojure team)20:05:10

sequence is an incrementally computed transducer context, but it's still not as lazy as lazy seqs

James Amberger21:05:19

ok, thanks for discussion

Ben Sless08:05:10

Why is sequence not as lazy as a lazy sequence?

Alex Miller (Clojure team)12:05:07

It fully computes intermediate results

Alex Miller (Clojure team)12:05:49

A good example is an expanding transducer like mapcat - if the map part produces a coll of 1000 elements, with sequence you will compute all of them at the step (including potentially infinite intermediate steps), whereas lazy seq is fully lazy there (modulo chunking)

Ben Sless14:05:34

Huh, is that desirable?

Ben Sless14:05:31

I mean, could sequence be modified to be fully lazy?

Ben Sless08:05:12

It all makes sense, I still wonder if it's desirable. Is it possible to write a "properly" lazy cat transducer? Should it exist?