Recovering Hypermedia with HTMX

Micah Yoshinaga · Wed Dec 06 2023

hypermedia-systems-cover.png

Ever since the creation of AngularJS in 2009 and even more so the creation of React in 2013, JS developers have encountered an onslaught of client-side-heavy frameworks, typically in the form of the Single Page Application (SPA). Around such frameworks grew an ecosystem of libraries, plugins, meta-frameworks, and build systems, and if you've started web dev during or after this "enculturation", there's a fair chance that you've been building applications on top of all these layers while never having to deal with the underlying system architecture that the web is built on.

Back to Basics: What is Hypermedia?

Perhaps you've seen this word before in your web dev journey, or perhaps it sounds like some other hyper related words like the hyperlink or the Hypertext Transfer Protocol. But what do those terms refer to? Simply put, hypermedia is a type of system that allows for the exchange of information that may include hypermedia controls (in the web, those are the <a> and <form> HTML elements) which allow users to non-linearly go from one piece of hypermedia to another, thus forming a dynamic infostructure...kind of like a spiderweb.

What's more, the ideas and history of this system architecture stems all the way back, as far as I can tell, to an article published by one Vannevar Bush originally in 1945, which you can read by clicking this hypermedia control, also known as the hyperlink, here.

Why am I telling you all this? Why the long prelude? Well it's a way to frame the approach that HTMX uses in contrast to the approach that other framework authors take. Though man-made things are never perfect, we ought to be cautious when ignoring or replacing long-lived things from the past without at least truly understanding them first. This has happened with the web and hypermedia. What's worse is the cavalcade of JS frameworks that incidentally created the illusion to a lot of web developers that "the only way to do X, Y, and Z is with React".

HTMX

Here's where HTMX comes in. The library is fairly small (around 14k when minified and gzip'd), and while it does in fact use JS, it doesn't require writing/wiring your own JS in userland, and it's only used because there isn't much in the way of modifying the default HTML behavior itself without it. Let's take a look at simple example:

<button hx-post="/clicked"
	hx-target="next .output"
	hx-swap="innerHTML">
   Click Me
</button>
<div class="output"></div>

In plain English:

"When this button is clicked, issue a POST request to the /clicked endpoint, then take the HTML response and replace the inner content of the next .output sibling element with said response"

Three things to note:

  1. As you can see, no explicit scripting. A lot of application patterns can be accomplished with just a smattering of attributes you put on an element
  2. In my paraphrase I mentioned "the HTML response". What's implicit in this example is that what the server returns from the /clicked endpoint should be HTML, not JSON
  3. Unlike clicking on an <a> element or submitting a <form>, the response from an HTTP request will not trigger a full page refresh. Instead, the response will replace, or be append to, your desired element 

And there's not a lot of HTMX attributes to juggle. While there's around 30 of them, most application UI patterns can be achieved with 5-7 of them. As you can see, these go with the grain of how the browser and hypermedia systems work rather than papering over it with some bespoke framework that's subject to change faster than web/browser standards.

To elaborate on point #2 above, one major side-effect of using HTMX is that it requires you to move a lot of your logic and processing from the client to the server and simply return HTML. It may appear odd to some, but when you stop for a moment to think through it, it makes sense. If the JSON you're bringing to the client will ultimately be transformed into HTML, why not just return HTML in the first place?

Returning JSON makes sense in other contexts, say between service boundaries that you have no control over or between mediums like a data provider and a native mobile application. But if you own the "full stack", then it's a waste of time, resources, and processing power, not to mention unnecessary complexity.

What's more, the HTML you return (whether partials or entire views) can themselves also include HTMX directives, giving you a lot of the things that we once thought were only feasible in SPA's. There is no special SDK or package you need to install on your favored server-side framework of choice: you just return HTML!

Here's another more elaborate example. Here we have a search field that, as the user types, search results will start coming in dynamically (a.k.a. the Active Search pattern):

<body>
	<input type="search"
 		hx-post="/search"
 		hx-trigger="keyup changed delay:500ms, search"
 		hx-target="#search-results"
 		hx-swap="innerHTML"
 		hx-indicator=".htmx-indicator" />
	<div id="search-results"></div>
	<img class="htmx-indicator" src="/spinner.svg" />
</body>

In plain English:

  1. When the input element is changed or when a keyboard key has been released or when the synthetic "search" event is triggered, and after a 500ms delay (hx-trigger),
  2. Issue a POST request to the /search endpoint (hx-post)
  3. While the request is in-flight, toggle the .htmx-indicator element to show the user that something is happening. When the request resolves, successfully or not, hide the element (hx-indicator)
  4. When the HTML response comes back, place it within the #search-results element (hx-target and hx-swap)

That's a lot of functionality for a small "surface area"; not a lot of ceremony and boilerplate. You get basic loading-spinner functionality, event declaration/binding, and debouncing in an easy-to-read-and-write format. What's more, since these are attributes placed onto the HTML elements themselves and not in a separate script, we can easily see at a glance that the markup and behavior are linked together. This idea of keeping like things together is a design pattern called Locality of Behavior.

Okay, one last example, this one using only two attributes to make things more SPA-like:

<main hx-boost="true" hx-push-url="true">
	<a href="/">Home</a>
	<a href="/recipes">Recipes</a>
	<a href="/settings">Settings</a>
	<form action="/search" method="POST">
 		<input type="search" placeholder="Find stuff..." />
 		<button type="submit">Search</button>
	</form>
</main>

Let's talk about hx-boost first. When applied to an element, all descendant anchor and form tags will perform AJAX requests and replace the contents of the <body> with their responses. Think about that: without adding anything else to the markup, all these hypermedia controls would normally trigger a full page reload, and process the <head> area unnecessarily, will now just update the content without a page refresh.

Just one small issue though: the browser's URL will not be changed, meaning users won't be able to do any deep-linking to certain pages. That's where the other attribute, hx-push-url comes in. With this set on the parent element, this will push any fetched URL to the browser's history using the native Browser History API under the hood. With just two attributes and no external scripting, we can have a common UI design pattern that you'd typically see in JS frameworks sans the cruft.

But Sometimes You Need That Framework

While HTMX is able to solve certain problems in a much cleaner way than something like React, there is nevertheless times when you need to use a framework. HTMX doesn't set out to replace React or Vue or Svelte entirely, you can in fact use both HTMX and a JS framework for different parts of your application. Something like Excalidraw or Google Maps would definitely benefit from a JS framework, but there could be small areas like a settings or preferences section that can easily be wired up with HTMX. It's up to you and your team to make the call, and it should be based on the requirements of the project, among other things, and not merely "well everyone's using it".

Falling for the latest 'hot thing' yet again?

It hasn't escaped this author that for all the praise heaped on HTMX, the sneaking suspicion arises: Is this going to be the same hype cycle as seen with Angular, React, or other frameworks? I hope not. We've all seen good ideas, design principles, patterns, and simple libraries metastasize to ridiculous proportions in projects (see: DRY, Separation of Concerns as just two examples). What were once humble tools in our kit to address particular problems that required discernment to implement are now being seen as the target to achieve. I don't doubt that HTMX can also be ripped out of its "progressively enhance your web app by adhering to the principles of hypermedia" context and be used as the scene for the next coding horror story.

But having used HTMX and taking the time to be (re-)acquainted with the ideas that make the web what is, we can leverage what already exists rather than unnecessarily reinventing an inferior wheel. On top of that, the fact that we don't need to send a JSON response to the browser if all we're doing with it is to convert it to HTML means that we can cut the middle man out. If for nothing else, the clarity that HTMX provides and the shift in what's salient to us as web developers, is worth its weight in silver, whether you actually use it in your project or not.

For a more in-depth dive into this topic, I highly recommend the book written by the author of HTMX: Hypermedia Systems.

If you want to start getting your hands dirty and try it out, check out the official site.

micah-mugshot.jpg

Micah Yoshinaga

Micah is a programmer. Although usually found in .NET land, he likes to venture out to new and differing perspectives across both the front-end and back-end.

Take the Next Step in Your Digital Transformation

Concentrate on your brand, business, or project while we handle the software development.

Get in touch with Kava Up to learn more!

Contact Us

Contact Us

Required fields are marked with an asterisk*