Create a small Clojure CLI project by hand, wire source and test paths, add run and test aliases, and verify the project from the shell before adding more tooling.
A deps.edn project can start as a plain directory. That is part of the appeal: you do not need a generator to understand what exists on the classpath.
1mkdir orders-cli
2cd orders-cli
3mkdir -p src/orders test/orders resources
Target structure:
1orders-cli/
2├── deps.edn
3├── resources/
4├── src/
5│ └── orders/
6│ └── core.clj
7└── test/
8 └── orders/
9 ├── core_test.clj
10 └── test_runner.clj
This is intentionally close to a small Java project: production code, test code, resources, and one project configuration file.
deps.ednCreate:
1{:paths ["src" "resources"]
2 :deps {org.clojure/clojure {:mvn/version "1.12.5"}}
3 :aliases
4 {:run {:main-opts ["-m" "orders.core"]}
5 :test {:extra-paths ["test"]
6 :main-opts ["-m" "orders.test-runner"]}}}
Each line has a job:
| Entry | Job |
|---|---|
:paths ["src" "resources"] |
Production classpath roots |
org.clojure/clojure |
Clojure runtime/library dependency |
:run |
Main-entry command alias |
:test |
Adds tests to the classpath and runs a test runner |
Create src/orders/core.clj:
1(ns orders.core)
2
3(defn order-total [order]
4 (reduce + 0 (:line-items order)))
5
6(defn -main
7 [& _]
8 (println
9 (order-total {:line-items [10 15 25]})))
This keeps the calculation testable and leaves printing in -main.
Run:
1clojure -M:run
Expected output:
150
Create test/orders/core_test.clj:
1(ns orders.core-test
2 (:require [clojure.test :refer [deftest is testing]]
3 [orders.core :as core]))
4
5(deftest order-total-test
6 (testing "adds line item amounts"
7 (is (= 50 (core/order-total {:line-items [10 15 25]})))))
Create test/orders/test_runner.clj:
1(ns orders.test-runner
2 (:require [clojure.test :as test]
3 [orders.core-test]))
4
5(defn -main
6 [& _]
7 (let [{:keys [fail error]} (test/run-tests 'orders.core-test)]
8 (when (pos? (+ fail error))
9 (System/exit 1))))
Run:
1clojure -M:test
The explicit runner makes this tiny project CI-friendly because a failed test produces a nonzero exit code.
Before adding more dependencies, inspect the classpath:
1clojure -Spath
You should see src and resources. To inspect the test classpath:
1clojure -A:test -Spath
This habit helps Java engineers quickly diagnose namespace loading problems. Most early errors are path, namespace, or alias mismatches.
Add JSON support:
1{:paths ["src" "resources"]
2 :deps {org.clojure/clojure {:mvn/version "1.12.5"}
3 org.clojure/data.json {:mvn/version "2.5.2"}}
4 :aliases
5 {:run {:main-opts ["-m" "orders.core"]}
6 :test {:extra-paths ["test"]
7 :main-opts ["-m" "orders.test-runner"]}}}
Then require it from a namespace:
1(ns orders.core
2 (:require [clojure.data.json :as json]))
Adding a dependency changes the classpath. It does not automatically impose a build lifecycle.
Keep the first project small until these commands are boring:
| Need | Add later |
|---|---|
| Richer test runner | Kaocha or another team-standard runner |
| Packaging | tools.build or a team build script |
| Formatting/linting | cljfmt, clj-kondo, or repo-standard tooling |
| Editor REPL | Calva, Cursive, CIDER, or nREPL middleware |
| Deployment | Container, jar, or platform-specific release process |
Do not start by copying a large production deps.edn. Build your mental model first.
deps.edn project can be created by hand with a small directory tree.:paths controls the production classpath; aliases add task-specific paths and options.clojure -M:run for the application and clojure -M:test for the small test runner.clojure -Spath before blaming Clojure code.