4 ms·
If you are new to Clojure and would like to experiment with it in a way that is immediately useful, I highly recommend the Babashka runtime for scripting [0]. I
by neilyio 2y ago
If you are new to Clojure and would like to experiment with it in a way that is immediately useful, I highly recommend the Babashka runtime for scripting [0]. It's very fun, approachable, and one of the more polished parts of the Clojure ecosystem.
It's a particularly good entry point because unlike full-JVM Clojure it has a very fast startup time. Newcomers can use any file-watching /reloading tools (e.g. nodemon) that they're already familiar with to work with it interactively.
Hopefully, a enthusiastic user will graduate to using a REPL connection in their editor for a fully interactive setup. But newcomers tend not to do this... its an unfamiliar workflow to most, and can be pretty cumbersome to setup.
[0]: https://babashka.org https://babashka.org
- neilyio 2y agoAnd when you're ready to take the fun to the web, look no further than Biff: https://biffweb.com https://biffweb.com
- packetlost 2y agoI recently went through most of the Biff tutorial but found it seemed to be missing some parts later on that had me scratching my head. It was otherwise a very enjoyable experience and Biff seems like a great way to get a "batteries included" starting point for web (similar to something like Rails or Django, though maybe not as comprehensive as either of those).
- jacobobryant 2y agoMind sharing the parts of the tutorial you had trouble with, if you remember?
- packetlost 2y agoI'll see if I can go back and figure out where I lost track of things. I believe it was right around when making the message entry box functional. I ended up having to dig into the repo to see what I was missing.
- jacobobryant 2y agoGood to know--I'll do another run through the tutorial sometime and see if I notice anything. I know there are a couple things I need to update in the example repo at least.
- zarathustreal 2y agoI love this so much. The power of being able to connect a repl to your production instance and fix bugs instantly is hard to understate. It’s actually kind of hard to explain to devs used to deployment cycles measured in weeks
- fire_lake 2y agoHow is the fix committed once you are done? Does this run counter to immutable deployments?
- zarathustreal 2y agoDo not try to "commit" or "immutably deploy" lisp data/code. That's impossible. Instead.. Only try to realize the truth. There is no "commit" / "deploy".
- hi-v-rocknroll 2y agoAh neat. But darn, I was kinda hoping for supporting tools and packages with names like tannen, delorean, doc, mcfly, and strickland ;)
- neilyio 2y agoIt features a time-travel database, if that helps! https://biffweb.com/p/xtdb-compared-to-other-databases/ https://biffweb.com/p/xtdb-compared-to-other-databases/
- ungamedplayer 2y agoI don't know if op had chance to evaluate because they were outatime.
- yogthos 2y agoThe best part about Babashka is that it's really batteries included nowadays. I had to make a little UI to display some stats about an app at work, and decided to try using it with HTMX. Turned out to be a really good experience. Babashka has pretty much everything you need for a basic web app baked in, and HTMX lets you do dynamic loading on the page without having to bother with a Js frontend. Best part is that bb can start nREPL with `bb --nrepl-server` and then you can connect an editor like Calva to it and develop the script interactively. Definitely recommend checking it out if you need to make a simple web UI. Here's an example of a full fledged web app: #!/usr/bin/env bb (require '[clojure.string :as str] '[org.httpkit.server :as srv] '[hiccup2.core :as hp] '[cheshire.core :as json] '[babashka.pods :as pods] '[clojure.java.io :as io] '[clojure.edn :as edn]) (import '[java.net URLDecoder]) (pods/load-pod 'org.babashka/postgresql "0.1.0") (require '[pod.babashka.postgresql :as pg]) (defonce server (atom nil)) (defonce conn (atom nil)) (def favicon "data:image/x-icon;base64,AAABAAEAEBAAAAAAAABoBQAAFgAAACgAAAAQAAAAIAAAAAEACAAAAAAAAAEAAAAAAAAAAAAAAAEAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAP//AAD8fwAA/H8AAPxjAAD/4wAA/+MAAMY/AADGPwAAxjEAAP/xAAD/8QAA4x8AAOMfAADjHwAA//8AAP//AAA=") (defn list-accounts [{:keys [from to]}] (pg/execute! @conn ["select account_id, created_at from accounts where created_at between to_date(?, 'yyyy-mm-dd') and to_date(?, 'yyyy-mm-dd')" from to])) (defn list-all-accounts [_req] (json/encode {:accounts (pg/execute! @conn ["select account_id, created_at from accounts"])})) (defn parse-body [{:keys [body]}] (reduce (fn [params param] (let [[k v] (str/split param #"=")] (assoc params (keyword k) (URLDecoder/decode v)))) {} (-> body slurp (str/split #"&")))) (defn render [html] (str (hp/html html))) (defn render-accounts [request] (let [params (parse-body request) accounts (list-accounts params)] [:table.table {:id "accounts"} [:thead [:tr [:th "account id"] [:th "created at"]]] [:tbody (for [{:accounts/keys [account_id created_at]} accounts] [:tr [:td account_id] [:td (str created_at)]])]])) (defn date-str [date] (let [fmt (java.text.SimpleDateFormat. "yyyy-MM-dd")] (.format fmt date))) (defn account-stats [] [:section.hero [:div.hero-body [:div.container [:div.columns [:div.column [:form.box {:hx-post "/accounts-in-range" :hx-target "#accounts" :hx-swap "outerHTML"} [:h1.title "Accounts"] [:div.field [:label.label {:for "from"} [:b "from "]] [:input.control {:type "date" :id "from" :name "from" :value (date-str (java.util.Date.))}]] [:div.field [:label.label {:for "to"} [:b " to "]] [:input.control {:type "date" :id "to" :name "to" :value (date-str (java.util.Date.))}]] [:button.button {:type "submit"} "list accounts"]] [:div.box [:table.table {:id "accounts"}]]]]]]]) (defn home-page [_req] (render [:html [:head [:link {:href favicon :rel "icon" :type "image/x-icon"}] [:meta {:charset "UTF-8"}] [:title "Account Stats"] [:link {:href "https://cdn.jsdelivr.net/npm/bulma@1.0.0/css/bulma.min.css" :rel "stylesheet"}] [:link {:href "https://unpkg.com/todomvc-app-css@2.4.1/index.css" :rel "stylesheet"}] [:script {:src "https://unpkg.com/htmx.org@1.5.0/dist/htmx.min.js" :defer true}] [:script {:src "https://unpkg.com/hyperscript.org@0.8.1/dist/_hyperscript.min.js" :defer true}]] [:body (account-stats)]])) (defn handler [{:keys [uri request-method] :as req}] (condp = [request-method uri] [:get "/"] {:body (home-page req) :headers {"Content-Type" "text/html charset=utf-8"} :status 200} [:get "/accounts.json"] {:body (list-all-accounts req) :headers {"Content-Type" "application/json; charset=utf-8"} :status 200} [:post "/accounts-in-range"] {:body (render (render-accounts req)) :status 200} {:body (str "page " uri " not found") :status 404})) (defn read-config [] (if (.exists (io/file "config.edn")) (edn/read-string (slurp "config.edn")) {:port 3001 :db {:dbtype "postgresql" :host "localhost" :dbname "postgres" :user "postgres" :password "postgres" :port 5432}})) (defn run [] (let [{:keys [port db]} (read-config)] (reset! conn db) (when-let [server @server] (server)) (reset! server (srv/run-server #(handler %) {:port port})) (println "started on port:" port))) ;; ensures process doesn't exit when running from command line (when (= "start" (first *command-line-args*)) (run) @(promise)) (comment ;; restart server (do (when-let [instance @server] (instance)) (reset! server nil) (run)))