~ / track C / clojure ecosystem
Babashka scripting
BasicBabashka (bb) is a fast-starting Clojure interpreter for
scripting. It's native-compiled (GraalVM), starts in ~10ms, and ships
with batteries: HTTP client, JSON, YAML, EDN, filesystem, processes,
HTTP server, even SQLite. It's Clojure for shell scripts — the niche
that bash, Python, and Node usually fill.
Why it exists
The JVM starts in 1–3 seconds. That's fine for a long-running web service. It's painful for a 50-line script you run from a Makefile. Babashka closes the gap:
$ time bb -e '(+ 1 2 3)'
6
real 0m0.012s
That's faster than python -c "print(1+2+3)" on most machines.
Babashka is built with GraalVM's native-image, so there's no JVM
warmup.
A real script
#!/usr/bin/env bb
(require '[babashka.fs :as fs]
'[babashka.process :refer [shell]]
'[cheshire.core :as json])
(def files
(->> (fs/glob "." "**/*.json")
(filter #(< (fs/size %) 1000000))))
(doseq [f files]
(let [data (json/parse-string (slurp (str f)) true)]
(when (:archive data)
(shell "git" "mv" (str f) (str "archive/" (fs/file-name f))))))
(println "Moved" (count files) "files")
Shebang it (chmod +x) and you have a script you can drop into any
git repo, run from CI, schedule with cron.
What's included
| Domain | Library |
|---|---|
| Filesystem | babashka.fs (paths, glob, copy, watch) |
| Processes | babashka.process (shell, pipes) |
| HTTP client | babashka.http-client (built-in clj-http-like) |
| HTTP server | org.httpkit.server (yes, in BB) |
| JSON | cheshire.core |
| YAML | clj-yaml.core |
| Async | clojure.core.async |
| CLI parsing | babashka.cli |
| Tasks | bb.edn tasks (think make in Clojure) |
| SQL | next.jdbc + bundled SQLite driver |
| Tests | clojure.test |
See the book of bb for the full list.
Tasks (bb.edn)
;; bb.edn
{:tasks
{clean (fs/delete-tree "target")
build {:depends [clean]
:task (shell "npm" "run" "build")}
deploy {:depends [build]
:task (shell "rsync" "-a" "dist/" "prod:/var/www/")}}}
bb deploy # runs clean → build → deploy
bb tasks # lists all tasks
bb.edn is make for Clojure people — task definitions in Clojure
data with dependency ordering. No quoting hell, no GNU Make syntax.
What's not included
- No Java reflection at runtime — so no Java libs at the call site. (Pods bridge this — see below.)
- No AOT-compiled Clojure libraries that depend on JVM classes Babashka doesn't include.
- No threads-with-real-parallelism in the same way as JVM (futures exist; the underlying GraalVM substrate has limits).
- No core.async
goblocks are slower than on JVM.
For most scripting work, none of those matter.
Pods — extend with native code
A pod is a native binary that speaks a small protocol to the BB process. This lets you use Postgres, Datalevin, Kafka, AWS, and other libraries that wouldn't fit in the BB binary:
(require '[babashka.pods :as pods])
(pods/load-pod 'org.babashka/postgresql "0.1.0")
(require '[pod.babashka.postgresql :as pg])
(pg/execute! db ["select 1 as one"])
Pods are written in any language (Go, Rust, Clojure on JVM). They extend BB without bloating the core binary.
When to use BB
| You want to... | Reach for |
|---|---|
| Write a 30-line script for a git hook | bb |
Replace bash glue between curl, jq, and awk | bb |
| Build a CLI tool you'll distribute | bb (or bbin) |
| Quickly prototype an HTTP service | bb with org.httpkit.server |
| Run a long-lived web service handling load | JVM Clojure ([[web-stack-ring-reitit]]) |
| Use a Java library at runtime | JVM Clojure |
| Run on the same Clojure as your main app | Either — code can be .cljc |
.cljc for shared code
A .cljc file is shared between Clojure and ClojureScript — and also
runs on Babashka. So your domain logic can live in .cljc and be
unit-tested with bb while the production service runs on JVM.
;; src/myapp/pricing.cljc
(ns myapp.pricing)
(defn tax [amount rate] (* amount (+ 1 rate)))
;; scripts/repl.bb
#!/usr/bin/env bb
(require '[myapp.pricing :as p])
(println (p/tax 100 0.08))
Try it (informational)
Babashka isn't in the in-browser SCI REPL, but the syntax is the same:
Real-world
| Where | Use |
|---|---|
| Git hooks (pre-commit, pre-push) | Fast startup makes BB practical here |
| CI scripts (GitHub Actions, GitLab CI) | Replace bash with cross-platform Clojure |
| Internal CLI tools | Distributable via bbin install |
| Database migrations runner | BB + pod-postgresql |
| Webhook receivers | httpkit/run-server in 30 lines |
| Log processors | Tail a file, parse with regex, transform |
| Kubernetes operators (small) | Easier to maintain than bash for ops teams |
Cookbooks: clj-kondo-style linting tools | BB hosts clj-kondo's code itself |
| nbb | Node-flavored BB — same idea, Node platform |
| Squint / Cherry | Clojure-syntax → JS, no runtime |
Check yourself
? quiz
Which scenario fits Babashka best?
Exercise
Write a bb script find-todos.bb that:
- Globs all
*.clj,*.cljs,*.cljcfiles under the current dir. - For each, scans lines containing
TODOorFIXME. - Prints a table
file:line: commentsorted by file.
Now turn it into a bb.edn task:
bb find-todos
What did you have to add to bb.edn? Now try it on a public Clojure
repo (clone something small) and see the output. How much faster is
this than the equivalent in bash + grep?