How to build HTTP services with Pedestal using routes, interceptors, and current Clojure CLI project setup instead of legacy scaffolding.
Pedestal is most useful when your HTTP service needs an explicit request-processing pipeline instead of a thin handler chain. Its core idea is the interceptor queue: request and response work becomes a sequence of small units that can validate input, enrich context, enforce policy, and construct a response without collapsing everything into one large handler.
That architecture makes Pedestal a strong fit for APIs that have meaningful cross-cutting concerns such as authentication, auditing, content negotiation, tracing, and error shaping. It is less compelling when you only need a tiny CRUD layer and want the smallest possible abstraction surface.
For a new example, prefer the Clojure CLI with deps.edn instead of old Leiningen scaffolding. Current Pedestal docs are on the 0.8 line, where connectors are the preferred startup model:
1{:paths ["src" "resources"]
2 :deps {org.clojure/clojure {:mvn/version "1.12.4"}
3 io.pedestal/pedestal.service {:mvn/version "0.8.2-beta-1"}
4 io.pedestal/pedestal.http-kit {:mvn/version "0.8.2-beta-1"}}}
The exact version numbers will evolve, but the structure matters more than the snapshot:
srctools.buildThat keeps the example aligned with the current official Clojure toolchain instead of teaching a legacy bootstrap flow as if it were the only default.
Pedestal routes work best when they identify what happens, not when they hide business logic inside route definitions:
1(ns myapp.service
2 (:require [io.pedestal.http :as http]
3 [io.pedestal.http.route :as route]))
4
5(defn health-handler [_request]
6 {:status 200
7 :body {:status "ok"}})
8
9(def routes
10 (route/expand-routes
11 #{["/health" :get health-handler :route-name :health]}))
This is intentionally simple. A route should point at work, not become the work itself.
Pedestal’s strength is not “it can return HTTP responses.” Every web stack can do that. The strength is the interceptor model.
An interceptor can participate in:
1(ns myapp.interceptors
2 (:require [io.pedestal.interceptor :as interceptor]))
3
4(def request-id-interceptor
5 (interceptor/interceptor
6 {:name ::request-id
7 :enter (fn [context]
8 (assoc-in context [:request :request-id]
9 (str (java.util.UUID/randomUUID))))
10 :leave (fn [context]
11 (let [request-id (get-in context [:request :request-id])]
12 (assoc-in context [:response :headers "x-request-id"] request-id)))}))
That is the right mental model: interceptors are composable policy and workflow units wrapped around the actual business handler.
Recent Pedestal releases prefer connector maps over the older service-map-first startup style. The connector form is explicit without forcing the application through the older io.pedestal.http setup:
1(ns myapp.server
2 (:require [io.pedestal.connector :as conn]
3 [io.pedestal.http.http-kit :as hk]
4 [myapp.service :as service]))
5
6(defn start []
7 (-> (conn/default-connector-map 8080)
8 (conn/with-default-interceptors)
9 (conn/with-routes service/routes)
10 (hk/create-connector nil)
11 conn/start!))
This pattern matters because it makes the pipeline inspectable:
That is clearer than scattering behavior across middleware wrappers and global setup, and it reflects the direction Pedestal itself now documents. From there, custom interceptors can be layered onto routes or connector setup deliberately instead of becoming hidden global magic.
Pedestal is strongest when:
Pedestal is a weaker fit when:
The point is not that Pedestal is universally “more enterprise.” The point is that its abstractions pay off only when the service shape needs them.
The old comparison is “Pedestal versus Ring versus Compojure.” A more useful comparison is:
Pedestal is not mainly a routing novelty. It is a request-lifecycle architecture.
flowchart LR
A["Incoming Request"] --> B["Enter Interceptors"]
B --> C["Route Match and Handler"]
C --> D["Leave Interceptors"]
D --> E["HTTP Response"]
The key thing to notice is that the handler sits in the middle, not at the whole center of the design. Interceptors before and after it shape most of the operational behavior.
deps.edn examples over legacy lein new scaffolding.