Examples

HTML with Hiccup & Integrant

Follow a complete HTML application from configuration through components, routes and rendered views.

Build and run

Requires Java 21+, Clojure CLI and the runtime named below. The example pins the compiler that supports native ^:async and await. Run these commands in a separate terminal from other examples.

git clone https://github.com/OUTCASTGEEK-TECH/fast-twitch.git
cd fast-twitch
git checkout a4eb73f96836d034c7a24fe09dc7d51e4c581f64
cd examples/02-hiccup-integrant-bulma
npm install
bb run-node

Request it

curl -i http://127.0.0.1:6464/
curl -i http://127.0.0.1:6464/hello/Ada
curl -i http://127.0.0.1:6464/missing

The first two requests return HTML with status 200. The third renders the application's missing-page view with status 404. /hello/Ada passes "Ada" to views/hello-page through :path-params.

Follow the request

  1. config declares settings and ig/ref dependencies. ig/init starts the graph in dependency order.
  2. ::settings returns the settings map. ::pages closes over it and returns three handler functions.
  3. ::routes produces an ordinary route vector. ::app passes that vector and the missing-page handler to routing/routes.
  4. routing/ft-handler adapts the Ring handler to Fetch. run starts the runtime on port 6464.
  5. html-response renders Hiccup through replicant.string/render, producing an HTML string in a normal response map.

Read server.cljs and then views.cljs below. The views file defines the page structure; the server file connects that structure to requests. Bulma is a linked stylesheet, not part of Fast-Twitch's routing API.

Change it yourself

Add a :goodbye handler to ::pages and a /goodbye/:name entry to ::routes. Render the name through a Hiccup text node. Rebuild, request /goodbye/Ada, and confirm that a different name changes the page without changing the route definition.

Lifecycle and other runtimes

bb run-bun and bb run-deno compile and run the same app. bb deno-serve builds a separate Fetch export without CLI startup. PORT=7777 bb run-node changes the port. The example's shutdown stops the server; if you add resources to Integrant components, implement corresponding halt methods rather than relying on page functions to release them.

HTML composition guide · Routing API.

Complete source and build files

The files below are copied without abbreviation from Fast-Twitch revision a4eb73f96836d034c7a24fe09dc7d51e4c581f64. The walkthrough and API reference use the same revision.

Source: examples/02-hiccup-integrant-bulma/src/hiccupapp/server.cljs

View this exact file on GitHub.

(ns hiccupapp.server
  [:require-macros [fast-twitch.macros :refer [env-var]]]
  [:require [fast-twitch.routing :as routing :refer [start-server! stop-server!]]
            [integrant.core :as ig]
            [replicant.string :as html]
            [hiccupapp.views :as views]]
  [:refer-global :only
                 [Number console globalThis]])

(def request-options
  {:protocol "HTTP/1.1" :remote-addr "127.0.0.1"})

(defn html-response
  [page]
  {:status 200
   :headers {"Content-Type" "text/html; charset=utf-8"}
   :body (html/render page)})

(defn not-found-page
  [settings uri]
  (assoc (html-response (views/not-found-page settings uri)) :status 404))

(defmethod ig/init-key ::settings
  [_ options]
  options)

(defmethod ig/init-key ::pages
  [_ {:keys [settings]}]
  {:home (fn [_request]
           (html-response (views/home-page settings)))
   :hello (fn [{:keys [path-params]}]
            (html-response (views/hello-page settings (:name path-params))))
   :not-found (fn [{:keys [uri]}]
                (not-found-page settings uri))})

(defmethod ig/init-key ::routes
  [_ {:keys [pages]}]
  [{:pattern "/" :method :get :handler (:home pages)}
   {:pattern "/hello/:name" :method :get :handler (:hello pages)}])

(defmethod ig/init-key ::app
  [_ {:keys [routes pages]}]
  (routing/routes routes (:not-found pages)))

(def config
  {::settings {:title "Hiccup through Integrant"
               :bulma-css "https://cdn.jsdelivr.net/npm/bulma@1.0.4/css/bulma.min.css"}
   ::pages {:settings (ig/ref ::settings)}
   ::routes {:pages (ig/ref ::pages)}
   ::app {:routes (ig/ref ::routes) :pages (ig/ref ::pages)}})

(defonce system
  (ig/init config))

(def handler
  (routing/ft-handler (::app system) request-options))

(defn shutdown
  []
  (stop-server! :force true
                :callback (fn []
                            (console.log "Server stopped"))))

(defn run
  []
  (start-server! handler
                 {:port (Number (or (env-var "PORT") 6464))
                  :hostname (or (env-var "HOSTNAME") "127.0.0.1")
                  :onListen (fn [addr]
                              (console/log
                                (str "Hiccup Integrant server listening at http://"
                                       (aget addr "hostname")
                                     ":" (aget addr "port"))))}))

(set! *main-cli-fn* run)

(defn ^:export fetch
  [request]
  (handler request))

Source: examples/02-hiccup-integrant-bulma/src/hiccupapp/views.cljs

View this exact file on GitHub.

(ns hiccupapp.views)

(defn layout
  [{:keys [title bulma-css]} & body]
  [:html
   [:head [:meta {:charset "utf-8"}]
    [:meta {:name "viewport" :content "width=device-width, initial-scale=1"}]
    [:title title] [:link {:rel "stylesheet" :href bulma-css}]]
   [:body
    [:section.hero.is-link
     [:div.hero-body
      [:div.container [:p.subtitle "fast-twitch newbie example"] [:h1.title title]]]]
    [:main.section [:div.container body]]]])

(defn home-page
  [settings]
  (layout settings
          [:div.columns
           [:section.column [:h2.title.is-4 "Hiccup is data"]
            [:div.box [:p "This box came from a vector in ClojureScript source code."]
             [:pre "[:div.box\n [:p \"This box came from a vector.\"]]"]]]
           [:section.column [:h2.title.is-4 "Integrant wires the parts"]
            [:div.box
             [:p "The config map builds settings, page handlers, routes, and the app."]
             [:a.button.is-link {:href "/hello/Newbie"} "Try /hello/Newbie"]]]]))

(defn hello-page
  [settings name]
  (layout settings
          [:div.box [:h2.title.is-3 (str "Hello, " name)]
           [:p "The name came from a path parameter in the request map."]
           [:p [:a {:href "/"} "Back home"]]]))

(defn not-found-page
  [settings uri]
  (layout settings
          [:div.notification.is-warning [:strong "No route matched "] [:code uri]]))

Source: examples/02-hiccup-integrant-bulma/deps.edn

View this exact file on GitHub.

{:aliases {:nREPL {:extra-deps {cider/cider-nrepl {:mvn/version "0.62.2"}
                                cider/piggieback {:mvn/version "0.7.0"}
                                nrepl/nrepl {:mvn/version "1.7.0"}}
                   :main-opts
                     ["-m" "nrepl.cmdline" "--middleware"
                      "[cider.nrepl/cider-middleware, cider.piggieback/wrap-cljs-repl]"]}
           :test {:extra-paths ["test"]}}
 :deps {fast-twitch/fast-twitch {:local/root "../.."}
        integrant/integrant {:mvn/version "1.0.1"}
        no.cjohansen/replicant {:mvn/version "2026.07.1"}
        org.clojure/clojurescript {:git/sha "56a94ac13bfac22108e4d868dfd4763edaceb864"
                                   :git/url "https://github.com/clojure/clojurescript"}}
 :paths ["src"]}

;; Start the example nREPL server
;; clj -M:nREPL

Source: examples/02-hiccup-integrant-bulma/app.edn

View this exact file on GitHub.

{:aot-cache true
 :cache-analysis true
 :dump-core true
 :fn-invoke-direct true
 :infer-externs :all
 :language-in :es-next
 :language-out :es-next
 :main hiccupapp.server
 :optimizations :simple
 :optimize-constants true
 :output-dir "build"
 :output-to "server.cjs"
 :output-wrapper "%s\nexports.fetch = hiccupapp.server.fetch;\n"
 :parallel-build true
 :pretty-print true
 :source-map "server.cjs.map"
 :stable-names true
 :static-fns true
 :target :nodejs}

Source: examples/02-hiccup-integrant-bulma/bb.edn

View this exact file on GitHub.

{:tasks {build {:doc "Compile ClojureScript to server.cjs"
                :task (shell "clj" "-M" "-m" "cljs.main" "-co" "app.edn" "-c")}
         deno-serve {:doc "Build and serve the exported Fetch handler with Deno"
                     :task (let [port (or (System/getenv "PORT") "6464")
                                 jobs (or (System/getenv "DENO_JOBS") "1")]
                             (shell "clj"
                                    "-M"
                                    "-m"
                                    "cljs.main"
                                    "-co"
                                    "app.edn"
                                    "-co"
                                    (pr-str {:nodejs-rt false
                                             :output-dir "build/serve"
                                             :output-to "build/server-serve.cjs"
                                             :source-map "build/server-serve.cjs.map"})
                                    "-c")
                             (shell {:extra-env {"DENO_JOBS" jobs "PORT" port}}
                                    "deno"
                                    "serve"
                                    "--parallel"
                                    "-A"
                                    "--port"
                                    port
                                    "build/server-serve.cjs"))}
         run-bun {:depends [build]
                  :doc "Build and run server.cjs with Bun"
                  :task (shell "bun server.cjs")}
         run-deno {:depends [build]
                   :doc "Build and run server.cjs with Deno"
                   :task (shell "deno run -A server.cjs")}
         run-node {:depends [build]
                   :doc "Build and run server.cjs with Node"
                   :task (shell "node server.cjs")}}}

Source: examples/02-hiccup-integrant-bulma/package.json

View this exact file on GitHub.

{
  "name": "fast-twitch-03-hiccup-integrant-bulma",
  "private": true,
  "type": "commonjs",
  "scripts": {
    "build": "bb build",
    "node": "bb run-node",
    "deno": "bb run-deno",
    "bun": "bb run-bun"
  },
  "devDependencies": {
    "@cljs-oss/module-deps": "^1.1.1"
  }
}

Source: examples/02-hiccup-integrant-bulma/deno.jsonc

View this exact file on GitHub.

{
  "tasks": {
    "build": "bb build",
    "start": "bb run-deno",
    "serve": "bb deno-serve"
  }
}

Source revision a4eb73f96836d034c7a24fe09dc7d51e4c581f64

Built with Fast-Twitch · Integrant · Datastar

Search documentation

Search pages and API symbols

Start hereWelcome to Fast-TwitchRing HTTP applications and configured native clients and transports in ClojureScript.Start hereGetting startedCompile and run your first Fast-Twitch server.Core conceptsRequest & responseUnderstand the maps at the center of a Fast-Twitch application.Core conceptsRoutingMatch URL patterns and compose handlers.Core conceptsMiddlewareWrap handlers with focused request and response behavior.GuidesHTML & IntegrantCompose a UI system with Integrant and render Hiccup.