CodeToLive

Clojure Web Development

Clojure offers a powerful ecosystem for building web applications with simplicity and scalability. The stack typically includes Ring as the HTTP server abstraction, Compojure for routing, and various libraries for templating, database access, and more.

Core Libraries

  • Ring: HTTP server abstraction
  • Compojure: Routing library
  • Hiccup: HTML templating
  • Integrant: Component management
  • Reitit: Alternative router

Basic Ring Application


(require '[ring.adapter.jetty :as jetty])

(defn handler [request]
  {:status 200
   :headers {"Content-Type" "text/html"}
   :body "Hello World"})

(defn -main []
  (jetty/run-jetty handler {:port 3000}))
                

Compojure Routing


(require '[compojure.core :refer [defroutes GET POST]]
         '[compojure.route :as route])

(defroutes app
  (GET "/" [] "Home Page")
  (GET "/hello/:name" [name] (str "Hello " name))
  (POST "/submit" {params :params} (process-submission params))
  (route/not-found "Page not found"))
                

Middleware

Middleware wraps handlers to add functionality:

Basic Middleware


(defn wrap-logging [handler]
  (fn [request]
    (println "Received request:" (:uri request))
    (handler request)))

(def app (wrap-logging (wrap-params handler)))
                

Common Middleware


(require '[ring.middleware.defaults :refer [wrap-defaults site-defaults]])

(def app
  (wrap-defaults 
    app-routes
    (assoc site-defaults :security {:anti-forgery false})))
                

Templating with Hiccup


(require '[hiccup.page :refer [html5]])

(defn home-page []
  (html5
    [:head
     [:title "My Site"]]
    [:body
     [:h1 "Welcome"]
     [:p "This is my Clojure web app."]]))

(defn app [request]
  {:status 200
   :headers {"Content-Type" "text/html"}
   :body (home-page)})
                

REST API


(require '[ring.util.response :as resp]
         '[cheshire.core :as json])

(defroutes api-routes
  (GET "/api/users" []
    (-> (get-all-users)
        (json/encode)
        (resp/response)
        (resp/content-type "application/json")))
        
  (POST "/api/users" {body :body}
    (let [user (json/decode (slurp body))]
      (-> (create-user user)
          (resp/created (str "/api/users/" (:id user))))))
                

Database Integration

With HugSQL


(require '[hugsql.core :as hugsql])

(hugsql/def-db-fns "sql/queries.sql")

(defn get-user-handler [id]
  (let [user (get-user-by-id {:id id})]
    (if user
      (resp/response user)
      (resp/not-found "User not found"))))
                

With next.jdbc


(require '[next.jdbc :as jdbc]
         '[next.jdbc.sql :as sql])

(def db {:dbtype "postgres" :dbname "mydb"})

(defn create-user [user]
  (jdbc/with-transaction [tx db]
    (sql/insert! tx :users user)))
                

Component Management


(require '[integrant.core :as ig])

(def config
  {:app/db {:jdbc-url "jdbc:postgresql://localhost/mydb"}
   :app/server {:handler (ig/ref :app/handler)
                :port 3000}
   :app/handler {:db (ig/ref :app/db)}})

(defmethod ig/init-key :app/db [_ opts]
  (create-datasource opts))

(defmethod ig/init-key :app/server [_ {:keys [handler port]}]
  (jetty/run-jetty handler {:port port :join? false}))

(defmethod ig/init-key :app/handler [_ {:keys [db]}]
  (make-app db))
                

Error Handling


(require '[ring.middleware.resource :refer [wrap-resource]]
         '[ring.middleware.not-modified :refer [wrap-not-modified]])

(def app
  (-> app-routes
      (wrap-resource "public")
      (wrap-not-modified)
      (wrap-exception-handling)))
                

Testing


(require '[ring.mock.request :as mock])

(deftest test-app
  (testing "main route"
    (let [response (app (mock/request :get "/"))]
      (is (= 200 (:status response)))))
                

Deployment

Standalone Jar


:profiles {:uberjar {:aot :all
                     :main myapp.core
                     :uberjar-name "myapp.jar"}}
                

Docker


FROM clojure:openjdk-11-lein
COPY . /usr/src/app
WORKDIR /usr/src/app
RUN lein uberjar
CMD ["java", "-jar", "target/myapp.jar"]
                

Full Example Project


(ns myapp.core
  (:require [compojure.core :refer [defroutes GET POST]]
            [ring.adapter.jetty :as jetty]
            [ring.middleware.defaults :refer [wrap-defaults site-defaults]]
            [hiccup.page :refer [html5]]
            [next.jdbc :as jdbc]))

(def db {:dbtype "postgres" :dbname "mydb"})

(defn layout [& content]
  (html5
    [:head
     [:title "My App"]
     [:meta {:charset "utf-8"}]]
    [:body
     [:div#app content]]))

(defn home-page []
  (layout
    [:h1 "Welcome"]
    [:p "This is the home page."]))

(defroutes app-routes
  (GET "/" [] (home-page))
  
(def app
  (wrap-defaults app-routes site-defaults))

(defn -main []
  (jdbc/with-connection [conn db]
    (jetty/run-jetty app {:port 3000})))
                

Next Steps

You've completed the Clojure tutorial series! To continue your learning:

  • Build a complete web application
  • Explore ClojureScript for frontend development
  • Learn about performance optimization
  • Contribute to open source Clojure projects