Clojure Guide — Web Development: Building Web Applications
In this tutorial, you will learn about Clojure Guide. We cover key concepts, practical examples, and best practices to help you master this topic.
Clojure web development is built on the Ring library, a minimalist HTTP abstraction that treats requests and responses as immutable maps, with middleware functions composing request handling pipelines.
What You'll Learn
- The Ring architecture: requests and responses as maps
- Middleware composition for cross-cutting concerns
- Routing with Compojure
- HTML templating with Hiccup
- Server configuration with Jetty
Why It Matters
Ring's functional approach to HTTP is elegant and testable. Every request handler is a pure function: map in, map out. Middleware wraps handlers with logging, authentication, and error handling. Doda Browser uses similar patterns for its extension API.
Real-World Use
CircleCI's web API is built with Clojure. Walmart's e-commerce platform uses Clojure Web Services. Puppet's Clojure-based tools use Ring for HTTP APIs.
flowchart LR
A["Web Dev"] --> B["Ring"]
B --> C["Handler"]
C --> D["Middleware"]
D --> E["Routing"]
A:::current --> B
style A fill:#2563eb,stroke:#2563eb,color:#fff
style B fill:#dbeafe,stroke:#2563eb,color:#1e40af
style C fill:#dbeafe,stroke:#2563eb,color:#1e40af
style D fill:#dbeafe,stroke:#2563eb,color:#1e40af
style E fill:#f1f5f9,stroke:#94a3b8,color:#64748b
The Ring Request Map
Every HTTP request becomes a Clojure map:
{:server-port 8080
:server-name "localhost"
:remote-addr "127.0.0.1"
:uri "/hello"
:query-string "name=Alice"
:scheme :http
:request-method :get
:headers {"host" "localhost:8080"}
:body #object[java.io.InputStream]}
The Ring Response Map
Every handler returns a response map:
{:status 200
:headers {"Content-Type" "text/html"}
:body "<h1>Hello World</h1>"}
;; JSON response
{:status 200
:headers {"Content-Type" "application/json"}
:body "{\"message\":\"Hello\"}"}
;; Redirect
{:status 302
:headers {"Location" "/login"}
:body ""}
A Simple Handler
(require '[ring.adapter.jetty :as jetty])
(defn handler [request]
{:status 200
:headers {"Content-Type" "text/plain"}
:body (str "Hello " (:query-string request))})
;; Start server
(def server (jetty/run-jetty handler {:port 8080 :join? false}))
Middleware
Middleware is a function that takes a handler and returns a new handler:
;; Wrap with logging
(defn wrap-logging [handler]
(fn [request]
(println "Request:" (:uri request))
(let [response (handler request)]
(println "Response:" (:status response))
response)))
;; Wrap with content-type
(defn wrap-content-type [handler content-type]
(fn [request]
(let [response (handler request)]
(assoc-in response [:headers "Content-Type"] content-type))))
;; Compose middleware
(def app
(-> handler
wrap-logging
(wrap-content-type "text/html")))
Error Handling Middleware
(defn wrap-exception [handler]
(fn [request]
(try
(handler request)
(catch Exception e
{:status 500
:headers {"Content-Type" "text/plain"}
:body (.getMessage e)}))))
Session Middleware
(require '[ring.middleware.session :refer [wrap-session]])
(defn handler [{{session :session} :params}]
(let [count (get session :count 0)
session' (assoc session :count (inc count))]
{:status 200
:session session'
:body (str "Visit count: " count)}))
(def app (wrap-session handler))
Static Files
(require '[ring.middleware.file :refer [wrap-file]])
(def app
(-> handler
(wrap-file "public")))
Common Mistakes
1. Mutating the request map
Request maps are immutable. Use assoc, update, or merge to create modified copies.
2. Forgetting to close the body
The request body is an InputStream. Read it with slurp and close it properly in long-running handlers.
3. Blocking in handlers
Use future or core.async for long-running operations. Ring's thread pool is finite.
4. Missing content-type headers
Responses without explicit Content-Type may render as plain text or cause browser Parsing issues.
5. Leaking server details in errors
Wrap handlers with exception middleware to return generic 500 responses instead of stack traces.
Practice Questions
1. What is a Ring handler? A function that takes a request map and returns a response map. It is the core abstraction of Clojure web development.
2. How does middleware compose? Middleware wraps a handler to add behavior. Multiple middleware layers compose via functional composition, each wrapping the previous.
3. Why is the request/response model functional? Because handlers are pure functions: same request always returns same response. Side effects must be explicit.
Challenge: Build a Ring handler with middleware for logging, authentication, and JSON responses.
FAQ
{{< faq question="Is Ring fast enough for production?" >}} Yes. Ring runs on Jetty, which is a production-grade Java servlet container. Many high-traffic services use Ring in production. {{< /faq >}}
{{< faq question="What web frameworks use Ring?" >}} Compojure, Luminus, Pedestal, and Reitit are all built on Ring. They add routing, middleware, and templates while keeping the core Ring abstraction. {{< /faq >}}
{{< faq question="Can I use Ring with async?" >}} Yes, Ring 1.7+ supports async responses. Use core.async channels to produce the response asynchronously. {{< /faq >}}
{{< faq question="How do I test Ring handlers?" >}}
Ring provides ring.mock.request for creating mock requests. Test handlers by calling them with mock requests and asserting on the response.
{{< /faq >}}
{{< faq question="Does Ring support WebSockets?" >}} Ring has basic Websocket support through Jetty. For production WebSocket use, consider Sente or http-kit. {{< /faq >}}
Mini Project
Create a simple JSON API with Ring:
(defn json-response [data]
{:status 200
:headers {"Content-Type" "application/json"}
:body (json/write-str data)})
(defn api-handler [request]
(case (:uri request)
"/api/health" (json-response {:status "ok"})
"/api/info" (json-response {:version "1.0" :uptime (System/currentTimeMillis)})
{:status 404 :headers {} :body "Not Found"}))
(def app
(-> api-handler
wrap-exception
wrap-logging))
(jetty/run-jetty app {:port 8080 :join? false})
What's Next
Now that you understand Ring, explore Compojure for elegant routing in web applications.
| Topic | Description | Link |
|---|---|---|
| Clojure Compojure | Routing library | {{< ref "22-compojure" >}} |
| Clojure Hiccup | HTML templating | {{< ref "23-hiccup" >}} |
Built by the developers of DodaTech
Doda Browser, DodaZIP & Durga Antivirus Pro