Tech Quips - htmx

In 2022 it is an expectation that not all interactions with a website trigger a full page reload. If this is a reasonable expectation or a good idea is a topic for another post. Usually this involves a relatively heavy frontend framework and a single page application or some vanillaJS or jQuery code which is maintainable by one to two people. For small projects it is often not worth the effort or simply not the core skill of the developer. Thankfully there is htmx.

htmx is a extremely small JavaScript library adding a handful of keywords to get, post, periodically reload data and a few other things. All while maintaining the regular request response paradigm most web frameworks are really good at. Render your HTML server side with all the nice little helpers, business logic and validation and replace parts of the website. All by using two to three new attributes on a div or button.

Let us take a look at the smallest example I could build in Golang and htmx.

package main

import (
	"crypto/rand"
	"html/template"
	"log"
	"net/http"
)

type ctx struct {
	Blk []byte
}

func index(rw http.ResponseWriter, req *http.Request) {
	if _, ok := req.Header["Hx-Current-Url"]; ok {
		render(rw, []string{"template/data.html"})
		return
	}
	render(rw, []string{"template/index.html", "template/data.html"})
}

func render(rw http.ResponseWriter, templates []string) {
	blk := make([]byte, 16)
	_, err := rand.Read(blk)

	if err != nil {
		log.Println(err)
		return
	}
	c := ctx{Blk: blk}

	tmpl, err := template.ParseFiles(templates...)

	if err != nil {
		rw.WriteHeader(http.StatusInternalServerError)
		rw.Write([]byte(err.Error()))
		return
	}

	err = tmpl.Execute(rw, c)

	if err != nil {
		rw.WriteHeader(http.StatusInternalServerError)
		rw.Write([]byte(err.Error()))
		return
	}
}

func main() {
	http.HandleFunc("/", index)

	log.Println("Listening on port 8080")
	log.Fatalln(http.ListenAndServe(":8080", nil))
}

Whenever we render the page we generate 16 random bytes and display them. Regular requests respond by rendering index.html while htmx requests - which can be distinguished from regular requests by the Hx-Current-Url header - only render data.html.

By all means a small change on the backend to allow rendering a partial template.

Our index.html includes the partial template and sets htmx up to fetch data every second.

<html>
  <body>
    <h1>Index</h1>
    <div hx-get="/" hx-trigger="every 1s">
      {{template "data.html" .}}
    </div>

    <script src="https://unpkg.com/htmx.org@1.8.0"></script>
  </body>
</html>

Our data.html takes care of the most beautiful presentation for 16 bytes I could think of in seven minutes.

<h2>Data</h2>
{{ .Blk }}

I have been using htmx a bit lately and it works exceptionally well. Not one hiccup, no issues with any data size I tried and the documentation is focused on the bare minimum you have to know to get it to work. It is actually refreshing to not read about the cool breeze on a summer morning which inspired the author to search for meaning in life by writing yet another JavaScript framework.

Considering how small the changes are to make htmx work and the more modern feel a web application has when actions do not trigger a full page reload it is wofrth taking a look. Especially considering it degrades nicely if JavaScript is disabled and allows you to use server side rendering.

>> posted on September 21, 2022 in #software-engineering #web