GO+HTMX, Why Not!
After some React fatigue (I shared a brief thought about it in React, my old Friend) I played around with HTMX. I built a small server for a budgeting app, all it does is list transactions, allow you to add new ones and change categories associated with each one. Without looking at the docs too much I was able to build some basics really quick. Listing and adding transactions was a breeze!
This feels familiar and if memory serves me well, I had a small project in college where I used PHP with templating to build a small back-office service. What's old is new again! The gist of it all is the ability to do little to no JS to build a web UI. With HTMX you decide which html data needs to be requested to your server so that it can directly replace a portion of your existing html.
As a trivial example, think of a list of TODO items, transactions, really any list.
<div>
<h2>Add Transaction</h2>
<form
hx-post="/add-transaction"
hx-target="#transactions-list"
hx-swap="beforeend"
hx-on::after-request="if(event.detail.successful) this.reset()"
>
<div>
<label for="transaction-vendor">Vendor</label>
<input type="text" name="vendor" id="transaction-vendor" />
</div>
<div>
<label for="transaction-amount">Amount</label>
<input type="float" name="amount" id="transaction-amount" />
</div>
<button type="submit">Submit</button>
</form>
<h2>Transactions</h2>
<ul id="transactions-list">
{{ range.Transactions }} {{ block "transaction-list-element" .}}
<li>
<span>{{ .Vendor }}</span>
<span>{{ .Amount }}</span>
</li>
{{ end }} {{ end }}
</ul>
</div>
If you pay attention to the form
element, that's where the fun begins. You specify that the form submission triggers a POST request. The result of that request will be appended to the element with id transactions-list
. On your server you then need the corresponding path to return the correct html:
mux.HandleFunc("/add-transaction", func(w http.ResponseWriter, r *http.Request) {
vendor := r.PostFormValue("vendor")
amountStr := r.PostFormValue("amount")
amount, err := strconv.ParseFloat(amountStr, 32)
if err != nil {
w.WriteHeader(http.StatusBadRequest)
return
}
tmpl := template.Must(template.ParseFiles("index.html"))
tmpl.ExecuteTemplate(
w,
"transaction-list-element",
Transaction{Vendor: vendor, Amount: float32(amount)},
)
})
and if you look at the network tab on your browser, the response is:
<li>
<span>test</span>
<span>12</span>
</li>
and HTMX takes care of the rest.
Thoughts
I will play with this more and try to push the boundaries of it. I realize that it will work pretty well for class-based CSS frameworks like Tailwind. And I appreciate the fact that it forces you to brush up on plain HTML.