Simple Apps with Clojure, Htmx and Pipelines

rockyj2 pts0 comments

Rocky Jaiswal Simple Apps with Clojure, HTMX and Pipelines<br>May 26, 2026

In my previous posts I have been going on and on about functional composition and pipelines and the “Result” pattern. This post is no different. Most of the examples I have shown are in TypeScript or Kotlin. In this post I want to show how these ideas translate naturally to Clojure and also make a case for a simple, server-rendered web stack that I think is enjoyable and simple - Clojure + Hiccup + HTMX .

The Modern Web Stacks

Most web application stacks today are complex. On the frontend we have React, a state management library, a bundler, TypeScript configuration and a build pipeline. On the backend we have a framework, an ORM, middleware configuration and a deployment pipeline. Even “simple” apps can easily have 5-6 moving parts and hundreds of lines of configuration before you write a single line of business logic. Not to mention multiple pipelines and deployment artifacts which need to sync up.

I am not saying this complexity is never warranted. For large, team-built, highly interactive applications it often is. But for a lot of web applications - internal tools, dashboards, CRUD apps, side projects - this is overkill. The question is: can we do better?

The Stack

The stack I want to talk about is -

Clojure as the language

Integrant for system lifecycle

Reitit for routing

Hiccup for HTML generation

HTMX for interactivity

PostgreSQL as the database (duh!)

Why Clojure?

Clojure sits on the JVM, so it has access to the entire Java ecosystem. But more importantly for our purposes, it is a functional language with immutable data by default, a phenomenal collections library and the -> threading macro which is essentially a built-in pipe operator (remember pipes).

The REPL-driven development workflow is also genuinely different from anything else I use. You start the system once and reload only changed namespaces. There is no “restart the server” cycle. Tight feedback loops without a hot-reload framework.

Why Hiccup?

Hiccup represents HTML as Clojure data structures (vectors). A div with a class and a child paragraph looks like -

[:div {:class "container"}

[:p "Hello world"]]

This is not a templating language. It is just Clojure. You can use map, filter, when, if - any function - directly inside your view code. You get the full power of the language without learning a separate templating syntax. And since it is just data, it is composable. View components are just functions that return vectors.

Why HTMX?

HTMX lets you add interactivity to server-rendered HTML by annotating elements with attributes. A button that loads content via AJAX looks like -

[:button {:hx-get "/items/1" :hx-target "#result"} "Load"]

The server returns a fragment of HTML and HTMX swaps it into the DOM. No JSON, no client-side state management, no Redux. For most CRUD operations this is all you need.

The combination of Hiccup and HTMX means the server always renders full HTML, the client only requests fragments, and all state lives on the server. This dramatically simplifies the mental model of the application.

The Architecture

The application I built follows a layered architecture -

HTTP Request

Controller -> parse request, call command, render HTML

Command -> orchestrate services using a Result pipeline

Service -> small, isolated domain functions

Repository -> database queries

parse request, call command, render HTML ↓Command -> orchestrate services using a Result pipeline ↓Service -> small, isolated domain functions ↓Repository -> database queries">

Each layer has one job. Controllers know about HTTP. Commands know about business flows. Services know about domain logic. Repositories know about SQL. Nothing bleeds into anything else.

One File, One Command

This is something I genuinely appreciate about this stack and it is easy to miss. The entire project is configured in a single deps.edn file - dependencies, aliases for dev, test, lint, format and build. No package.json, no webpack.config.js, no tsconfig.json, no separate frontend manifest. One file describes everything.

For development you run -

make repl

which starts a REPL. Then inside the REPL you type (go) to start the server. When you change a file you type (reset) and only the changed namespaces are reloaded. No process restart, no hot-reload plugin, no waiting. The feedback loop is as tight as it gets.

For production you build an uberjar -

make build

and deploy it with -

java -jar app.jar

That is it. No frontend build step, no asset pipeline, no synchronizing two deployment artifacts. Because Hiccup generates all HTML on the server, there is nothing to bundle. The whole application - routes, views, business logic, database access - ships as a single self-contained JAR. Compare this to a typical React + Node setup where you have a frontend build, a backend build, static asset hosting and a deployment pipeline that coordinates all three.

The simplicity is not...

clojure htmx server html build hiccup

Related Articles