Hacker Newsnew | past | comments | ask | show | jobs | submitlogin

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



And when you're ready to take the fun to the web, look no further than Biff: https://biffweb.com


I 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


How is the fix committed once you are done? Does this run counter to immutable deployments?


Do 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".


I 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).


Mind sharing the parts of the tutorial you had trouble with, if you remember?


I'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.


Good 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.


Ah neat. But darn, I was kinda hoping for supporting tools and packages with names like tannen, delorean, doc, mcfly, and strickland ;)


It features a time-travel database, if that helps!

https://biffweb.com/p/xtdb-compared-to-other-databases/


I don't know if op had chance to evaluate because they were outatime.


The 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)))


I agree. It's a breath of fresh air in the Clojure world. I'm grateful to thoughtful builders like yourself and borkdude for bringing the language to new heights.


Babashka is definitely the most exciting thing currently happening in Clojure world in my opinion. And thanks, always great to hear my stuff ends up being useful. :)




Guidelines | FAQ | Lists | API | Security | Legal | Apply to YC | Contact

Search: