Skip to content
AgileLogics
6 min read

Working with Databases

Prerequisites: Web services with Ring

Clojure talks to SQL databases through JDBC, and the standard library for that is next.jdbc. Results come back as vectors of plain maps — no ORM, just data.

Queries with next.jdbc

;; illustrative — deps: com.github.seancorfield/next.jdbc 1.3.1118 and a JDBC driver
(require '[next.jdbc :as jdbc]
         '[next.jdbc.sql :as sql])

(def ds (jdbc/get-datasource {:dbtype "postgresql" :dbname "shop"
                              :user "app" :password (System/getenv "DB_PASSWORD")}))

(jdbc/execute! ds ["CREATE TABLE IF NOT EXISTS product (id SERIAL PRIMARY KEY, name TEXT, price NUMERIC)"])

(sql/insert! ds :product {:name "Keyboard" :price 49.00M})
(sql/insert! ds :product {:name "Mouse" :price 19.00M})

(jdbc/execute! ds ["SELECT * FROM product WHERE price > ? ORDER BY name" 20])
;; => [#:product{:id 1, :name "Keyboard", :price 49.00M}]

(sql/update! ds :product {:price 17.50M} {:name "Mouse"})
(sql/find-by-keys ds :product {:name "Mouse"})
  • A datasource describes the connection; in production, wrap it in a connection pool (HikariCP via next.jdbc.connection).
  • SQL is written as a vector: the query string followed by parameters. ? placeholders are always used for values — never build SQL by concatenating strings, or you invite SQL injection.
  • Column names come back as qualified keywords (:product/name), which keeps data from different tables distinct.
  • Format and check your queries with the SQL Formatter.

Transactions

;; illustrative
(jdbc/with-transaction [tx ds]
  (sql/update! tx :account {:balance 70} {:id 1})
  (sql/update! tx :account {:balance 80} {:id 2}))

If anything inside throws, the whole transaction rolls back.

Building SQL as data

HoneySQL turns maps into SQL strings with parameters, which is handy when queries are assembled dynamically:

;; illustrative — deps: com.github.seancorfield/honeysql
(require '[honey.sql :as hsql])

(hsql/format {:select [:name :price]
              :from [:product]
              :where [:> :price 20]
              :order-by [:name]})
;; => ["SELECT name, price FROM product WHERE price > ? ORDER BY name" 20]

Migrations and other databases

  • Migratus or Ragtime manage schema migrations.
  • Datomic and XTDB are databases designed around immutable, time-aware facts, queried with Datalog — a natural fit for Clojure's data model.
  • For MongoDB, Redis, Kafka, and cloud services, use Clojure wrappers or the Java clients directly through interop.