~ / track C / clojure ecosystem

Babashka scripting

Basic

Babashka (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

DomainLibrary
Filesystembabashka.fs (paths, glob, copy, watch)
Processesbabashka.process (shell, pipes)
HTTP clientbabashka.http-client (built-in clj-http-like)
HTTP serverorg.httpkit.server (yes, in BB)
JSONcheshire.core
YAMLclj-yaml.core
Asyncclojure.core.async
CLI parsingbabashka.cli
Tasksbb.edn tasks (think make in Clojure)
SQLnext.jdbc + bundled SQLite driver
Testsclojure.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 go blocks 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 hookbb
Replace bash glue between curl, jq, and awkbb
Build a CLI tool you'll distributebb (or bbin)
Quickly prototype an HTTP servicebb with org.httpkit.server
Run a long-lived web service handling loadJVM Clojure ([[web-stack-ring-reitit]])
Use a Java library at runtimeJVM Clojure
Run on the same Clojure as your main appEither — 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:

loading sci
press ⌘/Ctrl-↵ or click ▶ run to evaluate

Real-world

WhereUse
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 toolsDistributable via bbin install
Database migrations runnerBB + pod-postgresql
Webhook receivershttpkit/run-server in 30 lines
Log processorsTail a file, parse with regex, transform
Kubernetes operators (small)Easier to maintain than bash for ops teams
Cookbooks: clj-kondo-style linting toolsBB hosts clj-kondo's code itself
nbbNode-flavored BB — same idea, Node platform
Squint / CherryClojure-syntax → JS, no runtime

Check yourself

? quiz

Which scenario fits Babashka best?

Exercise

Write a bb script find-todos.bb that:

  1. Globs all *.clj, *.cljs, *.cljc files under the current dir.
  2. For each, scans lines containing TODO or FIXME.
  3. Prints a table file:line: comment sorted 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?

 status: new