How to choose between Aleph and http-kit for Clojure HTTP work, with current dependency examples and realistic async server and client patterns.
Aleph and http-kit are both credible choices for Clojure HTTP work, but they feel different in practice. Aleph tends to fit systems that already embrace streams, backpressure, and asynchronous composition around Manifold and Netty. http-kit often fits teams that want a smaller surface area and a straightforward way to run an HTTP server or asynchronous client without carrying a heavier abstraction model.
The key decision is not “which library is faster in theory?” The better question is “which concurrency and operational model fits the service we are actually building?”
For a modern project, show dependencies in deps.edn. Keep the structure current, and pin the Aleph and http-kit coordinates to the latest releases from their project docs when you create a real project:
1{:paths ["src"]
2 :deps {org.clojure/clojure {:mvn/version "1.12.4"}
3 aleph/aleph {:mvn/version "REPLACE_WITH_CURRENT"}
4 http-kit/http-kit {:mvn/version "REPLACE_WITH_CURRENT"}}}
If the surrounding codebase still uses another build tool, consume the same libraries there. The important thing is that the example no longer assumes project.clj as the only default.
Aleph is built around Netty and works especially well when you need:
A minimal HTTP server still looks compact:
1(ns myapp.aleph-server
2 (:require [aleph.http :as http]))
3
4(defn handler [_request]
5 {:status 200
6 :headers {"content-type" "application/json"}
7 :body "{\"status\":\"ok\"}"})
8
9(defn start! []
10 (http/start-server handler {:port 8080}))
That is easy enough. The real benefit appears when the response is naturally asynchronous or streaming.
http-kit is often attractive when the team wants:
1(ns myapp.httpkit-server
2 (:require [org.httpkit.server :as http]))
3
4(defn handler [_request]
5 {:status 200
6 :headers {"content-type" "text/plain; charset=utf-8"}
7 :body "ok"})
8
9(defn start! []
10 (http/run-server handler {:port 8080}))
This is a good fit when the service’s main complexity lives in domain logic and persistence rather than in streaming or connection choreography.
Client examples should not stop at “here is how to send a GET.” The important production concerns are:
A simple http-kit asynchronous client call might look like this:
1(ns myapp.httpkit-client
2 (:require [org.httpkit.client :as client]))
3
4(defn fetch-json [url on-complete]
5 (client/get url
6 {:timeout 3000
7 :headers {"accept" "application/json"}}
8 (fn [{:keys [status body error]}]
9 (on-complete
10 (if error
11 {:transport-error true
12 :message (.getMessage error)}
13 {:status status
14 :body body})))))
The value here is not the callback itself. It is that the response shape is normalized so the rest of the program can reason about failure without knowing the raw library contract.
Aleph’s style becomes clearer when the service pipeline is already asynchronous:
1(ns myapp.aleph-client
2 (:require [aleph.http :as http]
3 [manifold.deferred :as d]))
4
5(defn fetch-body [url]
6 (-> (http/get url {:request-timeout 3000})
7 (d/chain (fn [response]
8 {:status (:status response)
9 :body (slurp (:body response))}))))
This kind of composition is attractive when your service already models downstream work as deferred values rather than immediate blocking steps.
Prefer Aleph when:
Prefer http-kit when:
The wrong choice is usually the one whose concurrency model the team does not actually want to own.
flowchart LR
A["Clojure Service"] --> B{"Need stream-heavy or deferred-first design?"}
B -- Yes --> C["Aleph + Manifold"]
B -- No --> D["http-kit"]
C --> E["HTTP, WebSocket, Streaming, Non-blocking Pipelines"]
D --> F["HTTP, Simpler Async Client/Server, Lower Surface Area"]
This is a better mental model than treating both libraries as interchangeable “fast web server” badges.
project.clj-only setup.