Skip to main content
Ben Nadel at cf.Objective() 2014 (Bloomington, MN) with: Sharon DiOrio
Ben Nadel at cf.Objective() 2014 (Bloomington, MN) with: Sharon DiOrio

Hypermedia Systems By Carson Gross

By
Published in , , Comments (11)

Last week, Peter Amiri - maintainer of the CFWheels framework for ColdFusion - sent me a hardcover copy of Hypermedia Systems by Carson Gross, Adam Stepinski, and Deniz Akşimşek. This book argues for the benefits of "Hypermedia Driven Applications" (HDA); and how leaning into the foundational nature of the web as a unified interface and hypermedia platform makes applications easier to build and more accessible while still achieving a high-bar of interactivity. This book is clear, concise, and quite compelling. Coming in at around 300 pages, I finished reading it in two day—I just couldn't put it down.

Ben Nadel holding a book titles Hypermedia Systems and looking rather excited about it.

Carson Gross, the primary author, is also the creator of a JavaScript framework called htmx. htmx—or, "hypertext markup extensions"—provides an attributes-based approach for adding rich interactivity to your web application. Abstractly, htmx seeks to fulfill four missing opportunities in the current implementation of HTML:

  • Opportunity 1: Allow any element to issue an HTTP request.

  • Opportunity 2: Allow any event to trigger an HTTP request.

  • Opportunity 3: Add the missing HTTP verbs (PUT, PATCH, DELETE).

  • Opportunity 4: Allow HTTP responses to be transcluded into the current DOM.

For example, you can configure a <button> element to dynamically replace sections of your webpage with freshly-fetched (AJAX) content using nothing but declarative hx-get and hx-target attributes.

Aside: if this technique sounds familiar to my readers it's because similar concepts are used by Hotwire Turbo; which, at the time of this writing, is what powers my ColdFusion blog.

The Hypermedia Systems book is about htmx; but, it isn't only about htmx. In fact, the entire first section of the book covers the history of the web, the concept of hypermedia as a unified interface, the true and original meaning of REST (REpresentational State Transfer), and makes the argument that HTML and HATEAOS (Hypermedia as the Engine of Application State) has many advantages over the extremely tight coupling required by today's Single-Page Application (SPA) frameworks.

This first section of the book is very compelling! When I started using Hotwire Turbo (an alternative to htmx), I was drawn to David Heinemeier Hansson's (DHH) vision of freedom; and the ability to build applications using the languages that you love. This same philosophy is very much true with htmx. But, the book comes at it from a different angle. More than just using the server-side languages that you love, the book argues that building applications using HTML as the driving force can lead to more resilient, more flexible, more forgiving, more accessible, and ultimately lighter-weight solutions.

As I read this first section of the book, I found myself nodding in violent agreement! The argument for this approach (HDA) is laid-out very clearly. And when the authors force us to think about how much we take hypermedia for granted, and enjoy the fact that it "just works", it feels almost laughable that our industry has committed so fully to JSON-based APIs.

Aside: to be clear, the authors of the book have nothing against JSON APIs for non-hypermedia-based clients (such as one server talking to another server). But, they posit that for the majority of browser-based applications, JSON-based APIs are unnecessarily complex and fragile.

In the second section of the book, the authors build a simple Multi-Page Application (MPA) that provides CRUD (Create, Read, Update, Delete) operations on a list of contacts. Then, they iterate on this MPA example, layering in htmx functionality to create richer and more dynamic interactions.

This second part of the book is very well crafted. The authors explicitly omit a lot of the code that would distract from the mission (for example, they omit all of the "model layer" implementation details). And, the code that they do include is laid-out with line-by-line explanations of what is happening.

The iterative nature of the second section really highlights the fact that leaning into HTML and hypermedia makes it relatively simple to evolve an application over time; especially since the browser itself doesn't have to know anything about the application domain model. In fact, most of the upgrades that they make to the contacts app are "progressive enhancements". That is, the application would continue to work correctly even if the JavaScript failed to load.

Aside: not all of the enhancements are "progressive"; and, the authors talk about this in the book. While they do try to use progressive enhancement techniques where ever they can, the authors accept the fact that some modes of interactivity simply need JavaScript to work; and, they are OK with that when used in measure.

Full disclosure, I skimmed the last section of the book which covers a hypermedia-based approach to building mobile applications. It sounds cool; but, I can only hold so much in my head. I'm happy to know that "Hyperview" exists; and, if and when I build a mobile application, I'll come back and read that section of the book in more detail.

Even if you don't use htmx, I highly recommend this book. If nothing else, it's a palette cleanser for those of us that have been dining on thick-client applications for the last 15 years. Reading it forces us to step back and question why we're using the technologies that we use; and, whether or not we can bring more nuance and insight to our decisions.

If nothing else, I'm kind of itching to throw hx-boost on a side-project and just see what happens!

SOAP BOX: I Couldn't Care Less About PUT, PATCH and DELETE

One of the arguments that the authors of the book make—and that many other people make—is that we are limited by HTML's constraint of GET and POST actions. I have personally never felt limited by this at all. And, I strongly believe that arguing over PUT vs PATCH vs whatever is nothing but pedantry. And, frankly, is a leaky abstraction in which the consumer of an API mistakenly believes that they have any idea what is actually going on under the hood.

The irony of the argument that we need PUT, PATCH, and DELETE is that the book also talks about "resources" as any uniquely addressable URL. Which means, if we can create unique URLs that represent "put", "patch", and "delete" intentions, then there's no actual need to have PUT, PATCH, and DELETE actions. And frankly, having the same URL react differently to different actions makes the server-side code more confusing.

It's also non-sense from a semantic perspective. As a thought experiment, imagine that you issue a DELETE request to a "contact" resource. If the server doesn't actually delete the given data but, instead, moves the data into a different tables (ex, contact to contact_archive), your DELETE operation was actually more like a POST.

Or, even more simply, what if your DELETE request did nothing but flip an isDeleted column or set a deleteAt time-stamp. In that case, your DELETE was really more like a PATCH operation. Of course, you didn't know that's what was going to happen.

And, that's my point! Stop pretending like you have any idea what the server is going to do with your request. All you need to know is that GET operations have the intention of reading state and POST operations have the intention of changing state. Anything beyond that is out of your control.

And now I'll get off my soap-box.

Reader Comments

257 Comments

Firstly, I loved this ..

If nothing else, it's a palette cleanser for those of us that have been dining on thick-client applications for the last 15 years.

Secondly, I'm so so so curious about digging into htmx, but just haven't ever made the time.

Thirdly, I love, use, and highly recommend CFWheels! I'm so grateful it exists and is still being actively maintained.

Sounds like a great book. Will add it to the list

15,973 Comments

@Chris,

It also looks like the have a free version online to read, if that helps. It is really fascinating stuff. I've been dabbling with Hotwire over the last year or so, with mixed feelings. But, I think so much of that is also that Hotwire uses Stimulus which is extremely verbose an HTMX seems to defer more to things like Alpine.js, which is much more concise (though with some trade-offs).

So much to let marinate in my brain.

re: CFWheels, I'm starting to use that now as well at work, so another thing to get up-to-speed on.

14 Comments

Ben,

I always love your perspective on these things. I got into htmx after working with cfml and Hotwire.

Its amazing how simple htmx makes the development paradigm. After I learned that Carson G also wrote idiomorph DOM morphing engine that rails / hotwire makes use of I thought I better check out htmx. For me it archives the same things that Hotwire does, with less convention over configuration and more declarative locality of behavior.

15,973 Comments

One thing that Carson said in the book that didn't sit right with me was is discussion about HTTP status codes. He recommended using 303 See Other instead of 302 Found (which is the default used by ColdFusion in-built location() function). His reasoning being that the 302 (and 301) status code propagated the incoming HTTP method onto the next URL.

This didn't seem right (practically); and I wanted to run a quick sanity check:

www.bennadel.com/blog/4770-sanity-checking-http-method-used-after-location-call-in-coldfusion.htm

All of the major browsers issue a GET in response to 301, 302, and303status codes. Even if using a303` is more "spec compliant", I can't imagine that the web will ever make such a breaking change.

15,973 Comments

@Peter,

Thank you for the kind words! That's cool that we wrote idiomorph. I've heard of it (I think a number of frameworks use it, or there might be a successor called ?dommorph?); but I haven't used it directly. He seems like he knows what he's doing!

I'm eager to try using it on one of my projects.

5 Comments

Hey Ben, I'm so happy you read up on HTMX. Like Hotwire, the concept isn't a framework or library, but a way of thinking.

Carson Gross and David Heinemeier Hansson have pointed out we don't need to follow Javascript framework conventions.

For example with a CFML server, you don't need a Javascript framework to build a SPA, just plain Javascript to call events to your server, then update the DOM.

No build process, no dependencies, no typescript.

However I think these benefits get lost with people who are accustomed to building stuff with React, Angular, and Vue. To them they are following what is popular, and find it productive.

The HTMX/Hotwire concepts come from experienced developers who built stuff long before Javascript went nuts. And who actually prefer using a server to handle the logic. We'll have to wait and see if the tide changes.

Now about your soap box...

Sorry, I strongly disagree. Using PUT, PATCH, and DELETE requests is a convention of REST. It's less about your app, and more about the consumer of your API. Having endpoints that handle different requests should not be confusing, just easier. It's about creating a standard convention consumers use to access your API.

All I can suggest is reading more on REST principles for building APIs. A GET request could be /books/1234, but the same endpoint could be a PATCH request, or DELETE request. There should be no confusion as your code "routes" the request based on the verb.

If there is confusion, it may have something to do with your framework.

Hope that helps!

15,973 Comments

@Bill,

I'm with you on wanting to drive as much logic to the server as possible. After all, there server is the source of truth; and, has to handle all of business logic and the validation anyway. So, if we can collocate more of the logic there, I think it just makes everything more simple to maintain.

I think the mindset shift is the hardest part to deal with. With a SPA, the nice thing is that it's at least really clear where things are, even if they are predominantly on the front-end. You have a given UI, and all the actions relevant to that UI are right there in the front-end code. With htmx / Hotwire / Livewire and such, my concern is that the logic starts to get spread over too many places.

What I mean is that it's no longer just a given button or link that is driving the behavior. Instead, a behavior may be driven by:

  • The link or button attributes.
  • Attributes inherited from some DOM elements higher-up.
  • Overriding logic in an event interceptor on the client.
  • HTTP Headers that override the target or swap behaviors.

What I'm hoping is that this level of flexibility is really just for the harder stuff; and that 95% of use-cases will be handled by simple button / link attributes. But, I can easily see myself spreading logic too widely while I'm still trying to get into an htmx mindset.

Re: REST, I think the crux of the disagreement is what you said here:

It's about creating a standard convention consumers use to access your API.

The way that I understand much of the htmx argument is that the "consumer" in an htmx application is the Browser. And the beauty of using HTML as the hypermedia is that the browser doesn't have to understand the response from the server. It just renders the response and follows the actions. So, the browser shouldn't have to care at all that given mutation is done via POST or DELETE - all it knows is that it was asked to render a button and the button submitted a form and the form asked the server to do something.

Then, on the server side, it all has to be different routes anyway (or, at the very least different control flows). So, whether or not you're doing:

DELETE /some/route

or

POST /some/route/delete

... you still have the same number of routes on the server; and the browser is still blindly making requests basked on the HTML that it received. The only different really is that in the former, you're having to monkey-patch the browser behavior to do something it doesn't really want to do while in the latter, you're just working with the grain of the browser.

So, in the end, you're not really getting a benefit from allowing DELETE. But, you're having to jump through more hoops to make it possible. And, I would argue—though this is subjective—that you're making your code harder to maintain since the same URL performs double-duty. Consider having to search the code base to see if anyone is using DELETE for /some/route - not only do you have to pattern match on the route itself, but also on the attribute that calling it. Searching teh code for /some/route/delete would be easier.

Now, to be clear, we're discussing this in the context of htmx and hypermedia clients. If you want talk about a server-to-server flow (such as me calling the Postmark or Twilio API), that's an entirely different conversation with different rules. In that case, sure, have a DELETE if you want - an API client has to be programmed explicitly so it doesn't matter one way or the other for maintainability.

5 Comments

Hey Ben,

You are right, and I admit, there is no hard fast rule in applying REST principles. Using only GET and POST requests can work, no argument there.

To me, it is more a matter of semantics. If my intention is to delete something, using a DELETE request instead of a POST request would at least make sense.

Same kind of thinking applies to CSS. There is no hard fast rule that says you can't use a header tag to style a footer, right?

But that's part of the art of being a developer. We all paint with different palettes and with different styles.

In regards to Javascript...

There have been some great minds providing comprehensive frameworks, that is for sure. Just fire up a boilerplate and you're off to the races. Next.js is a great example.

In that respect, HTMX falls short because it requires you to build your own framework using its principles. Less people are probably willing to do that.

That's why most CFML pros probably stick with FW/1 and ColdBox because it works and is productive. Javascript is secondary.

I don't know anything, but as a guess, I think a change would happen if a framework emerged that combined HTMX principles with a server side language... such as CFML.

Similar to Next.js, something that makes it really easy to build web applications with a reactive front-end but with logic in the back-end.

15,973 Comments

@Bill,

I would only caution about making a false equivalence here:

There is no hard fast rule that says you can't use a header tag to style a footer, right?

This is inherently a misleading decision (to use footer to describe header). In the HTTP world, this would be like using GET to perform mutations and POST to perform reads. I just want to make sure anyone reading this isn't subconsciously tempted to equate using POST /some/route/delete as being in the same realm as interchanging footer and header semantics. One is clearly wrong, the other is just a stylistic choice.

As I'm learning more about htxm and trying to read books, articles, and watch videos, the one thing that keeps coming up for me is the lack of URL-based state for much of the dynamics parts. So far, this has been a big mental hurdle for me that I'm not ready to give up on quite yet. In the same way that we want to keep things simple, I also want to keep things intuitive; which, in my mind means that the browser back button should work basically any time someone might expect it to. The problem I'm seeing though, is that so many UI changes in the htmx / hotwire / etc. world do not seem to push history into the URL. So, if a link opens a fly-out panel, there's no change in the URL; and hitting the back-button won't necessarily (or won't only) close the fly-out panel.

In the SPA world, this is comparatively easy because you can mutated parts of the URL programmatically.

I keep marinating on this in my mind, but I haven't come up with a solution yet.

5 Comments

Hey Ben,

Yes sorry, I was being rhetorical. And yes, one should always follow standard conventions. For me following semantics is also very helpful.

So if you prefer to use POST when your intention is to PATCH or DELETE, sure it works, but let's say we agree to disagree :)

Back to HTMX...

Yes, understanding pushState is key when building SPAs. The browser won't remember state history if you prevent default behavior.

To be honest, I don't use the HTMX library, so I can't speak on how they handle it. Instead, I wrote my own.

In a nutshell here is how it works...

Use the window.history.pushState(state, null, url) function to capture href history events.

Use the window.onpopstate function to watch for event.state changes. For example, clicking the back button.

What's nice is this can be written in plain old javascript.

Hope that helps!

15,973 Comments

@Bill,

I think one of the things that I keep struggling with is where to draw the line between progressive enhancement vs needing JavaScript to actually run the application. One thing that's "nice" about a SPA architecture is that the decision is already made for you; so, you don't have to spend too much time thinking about it 🤣 But, once we get into this enhanced multi-page application architecture, now you have to explicitly decided on every feature whether it can rely on native browser behaviors; or, if something will only work with JavaScript.

I'm hoping that once I get around to playing with stuff more, those lines will start to become clearing. In a perfect world, it would all just be progressive enhancement; but, I know that's not realistic as the features get more complex.

Post A Comment — I'd Love To Hear From You!

Post a Comment

I believe in love. I believe in compassion. I believe in human rights. I believe that we can afford to give more of these gifts to the world around us because it costs us nothing to be decent and kind and understanding. And, I want you to know that when you land on this site, you are accepted for who you are, no matter how you identify, what truths you live, or whatever kind of goofy shit makes you feel alive! Rock on with your bad self!
Ben Nadel