Thanks to visit codestin.com
Credit goes to github.com

Skip to content
/ borkweb Public

🥇 babashka`s first fullstack clojure framework. That works with jvm clojure. ❗Batteries included❗

License

m3tti/borkweb

Repository files navigation

Borkweb

Slack Join Slack

Borkweb: A Simpler Approach to modern Web Development

Borkweb is an effort to create a full-stack Clojure framework that is simple, minimal and traditional. I believe that the complexity and overhead of modern web development frameworks have made it difficult for developers to focus on building great applications. Our goal is to provide a tool that allows developers to work efficiently and effectively, without getting bogged down in unnecessary configurations and integrations.

Why Borkweb?

I didn't create Borkweb as a proof-of-concept or a side project. I created it because I needed it. I was building real applications and was frustrated with the complexity and overhead of existing frameworks. I wanted a tool that would allow me to focus on building great applications, without getting bogged down in unnecessary configurations and integrations. Borkweb is the result of my efforts, and I've been using it in production for my own applications.

Core Values

  • Simple: Borkweb is designed to be easy to use and understand, with a minimal learning curve.
  • Minimal: I believe that less is more. Borkweb has a small footprint and doesn't require a multitude of dependencies.
  • Traditional: I'm not trying to reinvent the wheel. Borkweb builds on established principles and best practices.
  • Flexible: Borkweb is designed to be adaptable to the needs of the developer. We avoid tying it too closely to conventions, allowing you to bend it to your will and use it as a starting point for your projects, rather than a rigid framework that restricts your creativity.

Full-Stack Clojure

Borkweb is a full-stack framework that uses Clojure on all ends. This means you can write your frontend code in ClojureScript using Squint, your CSS in Gaka, and your backend code in Babashka. Everything backed by a traditional SQL database.

No Overhead

One of the key benefits of Borkweb is that it requires minimal overhead. You don't need to worry about setting up a complex build pipeline or managing a multitude of dependencies. With Babashka, you can simply write your code and run it. No need for Node.js, no need for a separate frontend build process. Just write, run, and deploy.

Contributing

Borkweb is an open-source project, and I welcome contributions from anyone who is interested in helping to make it better. If you have an idea for a feature or a bug fix, please open an issue or submit a pull request.

Dependencies

Frontend third party

Get started

borkweb only needs babashka to get started. To run the template just do a bb -m core and you are good to go. If you want to have the login and register code up and running you should consider to create a database. Borkweb comes with HSQLDB by default. So there is no need to have a real database at hand. It is recommended for production workloads to use Postgres or other RDBMS systems. The HSQLDB is set to support the Postgres dialect to make it seamless to switch between development and production without the need for a RDBMS system on your dev host.

Everything starts with a Database

The initialization of the database is currently done with an initsql.edn file which you can trigger with bb -m database.core/initialize-db. The database connection parameters are available in database/core.cljs just replace as you like. Currently hsqldb and postgres are used for the database backend but you can basically use any sql database that you like and which is supported by your runtime. If you want to use a diffrent db like datalevin you'll lose the registration and login functionality and have to adjust some stuff. The initsql.edn is basically honeysql. You can lookup all special keywords and such at the documentation.

Routing

Routing can be done in the routes.clj file found in the base folder. There are already premade helper functions to generate routes in a compojuresque style.

(route path method (fn [req] body))
(get path (fn [req] body))
(post path (fn [req] body))
(option path (fn [req] body))
(put path (fn [req] body))
(delete path (fn [req] body))

CRUD Helper and Database simplifications

Borkweb tries to not stop you in your creativity and implementations. But sometimes you just want to get stuff done without thinking up front how to implement stuff and how it should look like. Sometimes you just need some CRUD tool fast out of the door. This is where Borkweb's CRUD helper functions come handy. Currently we have 2 places for some of those helpers

  • database/core.clj where you find common database operations like adding pagination, pagination with a search query or just simple CRUD operations.
  • utils/crud.clj here you'll find functions for the view part of your application. Like creating table views with all fields and adding buttons to routes where you create new entries. Furthermore handling of error cases for create, update and delete options.

CRUD Examples

(require [utils.crud :as crud])

(defn update
  [req]
  (crud/update!
   :req req
   :update-fn job/insert!
   :normalized-data (normalize-job-data req)
   :redirect-path "/job"))

(defn create
  [req]
  (crud/create!
   :req req
   :create-fn job/insert!
   :normalized-data (normalize-job-data req)
   ;; :does-already-exist? (some-check req) ;; optional
   :redirect-path "/job"))

(defn create&update
  [req]
  (crud/upsert!
   :req req
   :update-fn job/update!
   :create-fn job/insert!
   :normalized-data (normalize-job-data req)
   ;; :does-already-exist? (some-check req) ;; optional
   :redirect-path "/job"))

(defn delete [req]
  (crud/delete!
   :req req
   :delete-fn job/delete!
   :redirect-path "/job"))

;; routes.clj
...
(post "/save" create&update)
(post "/delete" delete)
...

New / Edit view

An example of how to use the create-update-form helper that generates a post form with the given inputs. Furthermore the example uses another helper out of the view/layout.clj that generates form-inputs with labels given by its type. You can extend it if you like. In this example we have added the trix editor with its own :type.

(crud/create-update-form
 :save-path "/post"
 :form-inputs
 (map l/form-input
  [{:type "hidden"
    :name "id"
    :value (:posts/id post)}
   {:label "Title"
    :type "input"
    :name "title"
    :value (:posts/title post)}
   {:label "Content"
    :type "trix"
    :name "content"
    :value (:posts/content post)}]))

Table View

Table View example with extra delete dialog button based on the modal feature present in borkweb.

[:div
 (crud/table-view
  :new-path "/post/new"
  :elements (post/all-paged q page)
  :edit-path-fn #(str "/post/" (:posts/id %) "/edit")
  :actions-fn
  (fn [e]
   [:div
    [:a.text-danger {:href "#"
                     :data-bs-toggle "modal"
                     :data-bs-target (str "#" (:posts/id e) "-delete")} "Delete"]
    (l/modal
     :id (str (:posts/id e) "-delete")
     :title "Delete Post"
     :content [:div "Delete Post really?"]
     :actions
     [:div
      [:button {:type "button" :class "btn btn-secondary" :data-bs-dismiss "modal"} "Close"]
      [:form.ms-2.d-inline {:action "/post/delete" :method "post"}
       (c/csrf-token)
       [:input {:type "hidden" :name "id" :value (:posts/id e)}]
       [:input.btn.btn-danger {:type "submit" :data-bs-dismiss "modal" :value "Delete"}]]])]))
 (l/paginator req page (post/item-count) "/post")]

FileUpload

(require [view.layout :as l])
(require [utils.encode :as encode])

(defn some-handler
  [req]
  (l/layout
   req
   [:form {:action "/some/action" :method "post"}
    (l/form-input
     :id "file-upload" ;; is mandatory
     :label "My Upload"
     :name "file")
    [:input {:type "submit" :value "save"}]]))

(defn some-post-handler
  [req]
  (let [file (encode/decode->file (get-in req [:params "file"] "/some/file.pdf"))]
    ;; do something
    ))

CLJS

borkweb provides already everything you need to get started with cljs no need for any bundler or anything else. Get to resources/cljs drop your cljs code that is squint compliant and you are good to go. borkweb allready includes some examples for preact and preact web components. There are helper functions to compile cljs code in your hiccup templates. You can find them in view/components.clj cljs-module to generate js module code and cljs-resource to create plain javascript code. there is even ->js which can be used to trigger squint/cljs code inline.

;; resources/cljs/counter.cljs
(require '["https://esm.sh/[email protected]" :as react])
(require '["https://esm.sh/[email protected]/hooks" :as hooks])

(defn Counter []
  (let [[counter setCounter] (hooks/useState 0)]
    #jsx [:<>
          [:div "Counter " counter]
          [:div {:role "group" :class "btn-group"}
           [:button {:class "btn btn-primary" :onClick #(setCounter (inc counter))} "+"]
           [:button {:class "btn btn-primary" :onClick #(setCounter (dec counter))} "-"]]]))

(defonce el (js/document.getElementById "cljs"))

(react/render #jsx [Counter] el)
;; view/some_page.clj
(require '[view.components :as c])

(defn some-page [req]
  [:h1 "My fancy component page"]
  [:div#cljs]
  ;; just use the filename without cljs. the function will search in the resource/cljs folder.
  (c/cljs-module "counter"))

Testing

One common task is to test your software. Therefore borkweb has its "custom" facility available. It is like all parts of borkweb completly related to clojure / babashka. We leverage clojure.test with a etoin (for e2e tests) and httpkit.clint (for api tests) and default clojure.test for unit test. Due to the fact that everything runs in clojure.test you can use all the tests you like to enhance your clojure test reporting and even run your test without the need of relying on other tools like playwright or cypress.

But borkweb alse features some helper to make your testing experience a little bit more convenient. All tests currently reside in the /tests folder in the project root and are included by default.

You can run the tests by using the bb run test:e2e or bb run test:api

e2e Testing

First of we are looking at the e2e part. Borkweb uses etaoin as its browser driver. Etaoin supports all default browsers you would expect. We use deftest to define our test cases. You don't have to setup any ceremony or stuff just write your tests. The driver is also initialized for you. By default we use chrome but you can change it with a simple change in the warmup function in the e2e.main namespace

(defn warmup []
  (reset! driver (e/chrome))) ;; change chrome to firefox or all other possibilities provided by etaoin

A typical test would look something like that but you can change and mix and match as you like. Borkweb gives you still the flexibility to change the framework to your wishes.

(deftest index-shown
    (e/go @driver config/base-url)
    (is (= (e/get-title @driver) config/base-url)))

Borkweb uses use-fixtures to make sure that the driver atom is filled with the correct driver for your test. So no need to start the driver or even close it.

api Testing

Api tests can be found under /tests/api/main.clj. For api testing borkweb uses httpkit.client and a small helper function to make your life easier with rest apis. A common test would look like this.

(deftest ping-endpoint
  (let [resp (request-json :get (str config/base-url "/api/ping"))]
    (is (= (:json resp) {"hello" "world"})))

Roadmap

  • add simple pwa functinality to make webapps based on borkweb installable
  • Add email interface to write and send emails in an easy manner
  • Add autocomplete component as webcomponent
  • Add hot reload functionality to cljs part of borkweb (long polling? Server side events?)
  • Add base64 upload code
  • Add e2e, api, unit testing
  • Exchange data with frontend components without an api (inline json?)
  • Add FileUpload Drop Area Component
  • Zip base64 data with zip.js
  • Add Html5 Modal window
  • Provide something like a repl for cljs/squint code. Maybe also directly in the browser to trigger functions @borkdude supported the repl option to squint which might be a good idea.
  • Provide a simple production ready docker-compose config with postgress, caddy (as reverse proxy), and the babashka app. Everything easily scaleable through replicas.
  • Support datalevin as second database engine with an easy switch via a setting.
  • make a port to common lisp one day 🎉

Articles

About

🥇 babashka`s first fullstack clojure framework. That works with jvm clojure. ❗Batteries included❗

Resources

License

Stars

Watchers

Forks

Releases

No releases published

Packages

No packages published

Contributors 2

  •  
  •  

Languages