HTMX Intrigue
htmx in a nutshell
htmx is a library that allows you to access modern browser features directly from HTML, rather than using javascript.
A few days ago and after having read a lot about HTMX and HATEOAS, I finally got my feet wet with HTMX.
I wanted to include the amount of comments and sats that I received on SN on my static site that is generated with Hugo:
The cool thing about HTMX is that you don't need to write javascript yourself. The HTML spec is extended with attributes like
hx-get
, hx-trigger
, hx-target
and more which declare the behavior you want.For example, this is the HTML fragment that I included in my HTML template:
{{ with .Params.sn_id }} <span hx-get="/api/content_meta?sn_id={{- . -}}" hx-trigger="load" hx-swap="outerHTML" > <span class="px-1">|</span> <span> <a class="underline" href="https://stacker.news/items/{{- . -}}" target="_blank" rel="noopener noreferrer me">0 comments</a> </span> <span class="px-1">|</span> <span>0 sats</span> <span> {{ end }}
On page load (
hx-trigger="load"
), a GET request to /api/content_meta?sn_id=<id> is triggered (hx-get
) and the response replaces <span>
(hx-target
).1For the backend, I went with go and
net/http
which is part of go's stdlib. I also learned about templ
which makes it possible to write something like JSX in go.2This is how I wrote the HTML:
package main import "github.com/ekzyis/snappy" import "fmt" templ contentMeta (item *sn.Item) { <span class="px-1">|</span> <span> <a class="underline" href={ templ.URL(fmt.Sprintf("https://stacker.news/items/%d", item.Id)) } target="_blank" rel="noopener noreferrer me" > { fmt.Sprintf("%d comments", item.NComments) } </a> </span> <span class="px-1">|</span> <span>{ fmt.Sprintf("%d sats", item.Sats) }</span> }
And then I ran
templ generate
in the terminal which generated a go file which includes a function that I can call in my request handler to render the HTML:func HandleContentMeta(w http.ResponseWriter, r *http.Request) { var ( // stacker.news item id qid = r.URL.Query().Get("sn_id") c = sn.NewClient() item *sn.Item id int err error ) w.Header().Add("Access-Control-Allow-Origin", "*") w.Header().Add("Access-Control-Allow-Methods", "GET") w.Header().Add("Access-Control-Allow-Headers", "*") if r.Method == "OPTIONS" { return } if qid == "" { w.WriteHeader(http.StatusBadRequest) fmt.Fprint(w, "sn_id query param required\n") return } if id, err = strconv.Atoi(qid); err != nil { w.WriteHeader(http.StatusBadRequest) fmt.Fprint(w, "sn_id must be numeric\n") return } if item, err = c.Item(id); err != nil { w.WriteHeader(http.StatusInternalServerError) fmt.Println(err) return } contentMeta(item).Render(r.Context(), w) }
The heavy lifting of fetching the SN item data is done with snappy, the library that I wrote for @hn and @nitter.
And that is all! Quite simple, ain't it?
I am really intrigued by HTMX now, even more now than I already was before trying it out. I suspected it wouldn't be that easy in practice. Of course, this was only very basic usage but at least this very basic usage is indeed very basic!
But now I wonder ... are there any HTMX devs here? If so, I'd be interested in your experience. Do you like it? What did you build with it? What do you not like about it?
Footnotes
<noscript>
to gracefully degrade the comment link to a comment link with no data about how many comments exist. Or maybe I don't even have to use<noscript>
, I can simply replace the existing fragment in the initial server response? 🤔 ↩