Come chat in #zodiac
Zodiac is a small web framework for Clojure that provides a reasonable set of defaults while also being easily extensible. Zodiac stands on the shoulders of giants rather than being innovative. At its core Zodiac is mostly just a preconfigured Ring app and not more that a few hundred lines of code.
Zodiac tries to fill a similar niche as the Flask framework with defaults that make it quick to start a new Clojure based web app without being heavy-handed.
What Zodiac includes by default:
- Routing and middleware. We use Reitit
- Request and response handing with Ring.
- A Jetty server (though Jetty can be turned off)
- Automatic Hiccup-based HTML rendering using Chassis.
- Websocket support
- File streaming
- Flash messages
- Cookies and secure session handler
- Form and JSON request parsing
- Extensible, see Zodiac Assets and Zodiac SQL
- Convenience
- Helpers to lookup routes
- Helpers to return hiccup and JSON responses
- A request context
- Variables dynamically bound to the current request, router and session
What Zodiac doesn't do:
- Dictate a file structure with generators or scaffolding.
- No configuration over code
- No path based routing, etc.
- Expect a certain database - (see Zodiac SQL)
- Asset bundling (see Zodiac Assets)
And that's about it. Zodiac is mostly feature complete although you can expect plenty of bug fixes and polish before a 1.0.x release.
Read the docs.
(ns myapp
(:require [zodiac.core :as z]))
(defn routes []
;; routes use the reitit route syntax
["/" {:handler (constantly {:status 200
:body "ok"})}])
(z/start {:routes #'routes})The zodiac.core/start function takes a single options map with the following keys:
:routes: The route definition using the reitit route syntax:extensions: A sequence of functions that accept an integrant system configuration map and return a modified integrant system configuration app.:request-context: A map of values values to add to the::z/contextmap in the request map.:cookie-secret: The secret used to encrypt the cookie:cookie-attrs: Override the code settings. Defaults to{:http-only true :same-site :lax}.:jetty: A map of options to pass torun-jettyfunction of the embedded ring-jetty-adapter:port: The port to listen for connections. If the port is also specified in the:jettymap then this value will be ignored. The default is3000.:error-handlers: A map of types to error handler functions:anti-forgery-whitelist: A sequence or strings or regular expressions to bypass anti-forgery/csrf checking for matching routes.:reload-per-request?: Reload the routes on every request. For this to work you will need to pass the var of the routes function, e.g.#'routes.:print-request-diffs?: Print a diff of each request between each middleware.:start-server?: Set tofalseto disable the embedded jetty server.:middleware: A list of ring middleware. Wraps, not replaces, the default middleware.:default-handler: Add a handler that gets called if a requested route doesn't match. Zodiac already includes a default handler created with create-default-handler. This option allows a handler to be called before the regular default handler gets called. For more information see the Default handler section in the Reitit docs.
Return a vector from the response handler to automatically convert the vector to an html response.
(ns myapp
(:require [zodiac.core :as z]))
(defn routes []
;; Returns a text/html response with <div>hi</div> for the body.
["/" {:handler (fn [_] [:div "hi"])}])
(z/start {:routes #'routes})Use the zodiac.core/json-response function to encode Clojure maps to JSON and return application/json HTTP responses.
(ns myapp
(:require [zodiac.core :as z]))
(defn routes []
;; Returns an application/json response with {"hello": "world"} for the body.
["/" {:handler (fn [_] (z/json-response {:hello "world"}))}])
(z/start {:routes #'routes})Zodiac can be extended using a sequence of functions that take an integrant system map and return a modified integrant system map.
(defn service-ext [cfg]
(-> cfg
;; Add a ::service component to the config map
(assoc ::service {:value "hi"})
;; Put an instance of the service in the request context
(assoc-in [::z/middleware :context :service] (ig/ref ::service))))
(defn routes []
;; routes use the reitit route syntax
["/" {:handler (fn [{:keys [::z/context]}]
{:status 200
:body (-> context :service :value)})}])
(z/start {:routes #'routes
:extensions [service-ext]})- Clojure Land: Discover open-source Clojure libraries and frameworks.
- Zodiac Todo App: A very basic todo app build with Zodiac, Zodiac Assets, Zodiac SQL, Sqlite, AlpineJS, Htmx, Tailwind and Vite.
- Websocket example: An example of how to uses websockets with Zodiac
-
Zodiac Assets: Static asset building and url lookup with Vite.
-
Zodiac SQL: Helper for connecting to SQL database and running HoneySQL queries.