pull down to refresh

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).1
For 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.2
This 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

  1. For example, this is the response for this post.
  2. JSX was my original mindblow of React
Do you like it?
I don't program in htmx, but I like the fact that it doesn't use javascript.
reply
37 sats \ 0 replies \ @ek OP 7 Jun
I like the fact that it doesn't use javascript
htmx is javascript, see here. but it allows that you don't have to use javascript
reply
This is great, thanks for sharing.
HTMX looks awesome and I've been wanting to work with it as well
reply
10 sats \ 1 reply \ @halleck 7 Jun
I'm in a similar boat. I am intrigued by htmx as a solution for simple web tools I maintain for work. I've done some simple proof of concept apps, but haven't taken the dive into actual production track code.
reply
Yeah, same here. Using it on my own site was just a proof of concept.
Currently, I see HTMX as a way to progressively enhance a website. By default, the server serves static HTML which should be really fast with something like nginx. If Javascript is enabled, HTMX tags will be used to make it more dynamic, at the cost of additional roundtrips vs SSR.1
I like this approach from keeping it as simple as possible and then gradually enhancing it. I also started to like constraints since they make me creative, especially when these constraints force me to keep it simple. Just serve HTML, no (immediate) need for hydration, client-side routing, state management on the client, serving a ton of Javascript etc. like is usual for SPAs built with frameworks like React.2
I've also read about using a serviceworker for HTMX that can intercept requests so you could serve HTML fragments even offline (assuming you can predict what the server would respond with). The biggest downside of HTMX seems to be that by default, it does a network request for everything but it at least eliminates full page reloads since it handles fragments.
So if you ever want client-side routing with HTMX, a serviceworker could be the way.

Footnotes

  1. I should use <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? 🤔
  2. The dark web is actually a great inspiration for this stuff. Markets in there work pretty decently (almost like SPAs) with no Javascript enabled.