This feels like digital photography
Ever since I first started including HTMX in my web projects and gaining a better understanding of hypermedia architecture, I’ve had one thought repeatedly float through my mind: this feels like digital photography.
I’m not talking about digital photography in the sense of a camera with a sensor that digitizes a real-life, analog scene into pixels on an SD card, but rather the very act of browsing the web. I’ve come to understand that users are taking photographs of the digital realm hundreds, if not thousands (hopefully not millions!) of times per day. We, as the developers, have the burden and the privilege to create the compositions that our users consume.
So, wouldn’t it be interesting if we reframed our practice as web developers in the same fashion that photographers fashion their own pictorial compositions?
What is hypermedia?
The word hypermedia is dense. Many of us have at least heard the word before, which is often a best case, but many of us also don’t know what it is or how to define it. Let’s change that.
For the purposes of this article, hypermedia is a markup-based representation of some kind of state, usually on your server, and that markup contains controls that tell a user agent (or more casually, a browser) what you can do next. HTML already does this with links and forms, but as web developers, we don’t have to limit ourselves to only these controls. More on that soon.
The lineage of hypermedia predates the web by decades. Ted Nelson coined the terms hypertext and hypermedia in the 1960s, and in 1968 Doug Engelbart famously demonstrated a working hypertext system during the ‘Mother of All Demos.’ He also demonstrated many other things we take for granted, like mouse hardware and point and click interaction in software.
It wouldn’t be until 1987 that the common computer user would have access to a hypermedia system.
Macintosh engineer Bill Atkinson began work on an application eventually known as HyperCard, which was partly inspired during an LSD experience. Atkinson negotiated with Apple to bundle HyperCard free with the Mac for two years, so it could establish itself using Apple’s huge distribution channels.
HyperCard popularized an accessible combination of visual authoring, hypermedia, and embedded scripting using a language called HyperTalk.
HyperTalk was an object-based, message-driven, event-driven scripting language that allowed HyperCard users to add interactivity and automations to their “stacks.” It was intended to be beginner-friendly, with an English-like syntax. Sample script commands might look like:
put 5 * 4 into theResult
Or
put the first card field into firstFieldValue
Or
ask “What is your name?”
HyperCard never made the jump from classic Mac OS to Mac OS X and was eventually leapfrogged by other interactive development environments you might still remember - Macromedia Flash, Dreamweaver, and Visual Basic.
Managing state is the hard part
Content, media, users, sessions, relationships - once an application becomes interesting, keeping track of its world gets complicated fast. So, we build abstractions to manage that world, which lets us spend our time on the fun part: composing it.
Managing state is a difficult, evergreen computer science problem that still incites vigorous debates today, right up there with naming things (ever heard of statecharts?). It’s only natural that the computer scientist looks for ways to ease the burden, and that often means introducing yet another layer of indirection.
The browser is our camera
But let’s get back to that photography metaphor.
In this metaphor, the browser is our camera. Users are repeatedly asking for views of a digital world, and we, as developers, compose those views.
And this is the part that makes hypermedia a little more interesting than standard print media. Our hypermedia photograph doesn't just show us the current state, it contains the controls for requesting the next virtual “photograph.” A link, a form, an hx-get — these are virtual shutter buttons embedded inside the representation itself. The user presses one, and we ask the server to compose another view of the world. Our server can also choose to manipulate an individual user’s journey with information stored in the database or their session.
It’s important to distinguish that our digital photographs contain state, but a photograph is not the state itself; it is a representation of that state. Managing that state is the hard part.
Umbraco is the studio
If the browser is our camera, Umbraco is our studio. It's where we stage and manage the world our visitors are going to photograph.
I've built applications in Django, Laravel and vanilla PHP. They're all perfectly capable web frameworks. But Umbraco gives us a surprisingly large amount of this world-management machinery out of the box, and for managing state so well over the course of the approximately last two decades, we reward it with: baseline expectations. But when it comes to hypermedia work, it really is batteries included.
Aside from providing sophisticated state management, Umbraco has another powerful hypermedia weapon – Surface Controllers.
Surface controllers are an Umbraco-specific affordance that sits atop the ASP.NET MVC framework, that provide access to Umbraco APIs and the active site context. They can return full-page responses, or partial view responses, and they can accept any of the HTTP verbs. We can also use query parameters and POST data to trim, tailor and compose the snapshot to our visitor’s needs and preferences.
Paired with HTMX, Umbraco gives you a rich environment for composing hypermedia responses because so much of the world you want to represent already has mature management tooling around it.
Locality of behavior
Let’s talk about composition.
For years we were taught separation of concerns meant putting markup in one file, separating styling in another, and behavior yet again, somewhere else. Locality of behavior challenges that practice, and will probably challenge you, but is a crucial tool in composing our digital photographic compositions.
Separating things by technology is not necessarily the same thing as separating them by concern.
Locality of behavior is a software development principle that encapsulates many facets of the behavior of a unit of code. With it, we aim to make functionality and appearance as obvious as possible by looking only at that unit of code.
Quite dense! If we rewire that in a more pragmatic context, it’s like saying if a button causes a request, there’s value in being able to look at the button itself and understand what it does and how it’s supposed to appear.
Fair warning: this is probably going to make many developers uncomfortable.
Let’s look at locality of behavior in a sample of code. We start with a simple HTML button, but we’ve also got styling here via the Tailwind CSS classes. We’ve also got behavior here, with hx-get, hx-target and hx-swap attributes. We’ve also got our hypermedia controls here. Twenty years of web-development conditioning tells us that this is a mess and we should avoid it.
But I can look at this unit of code and understand almost everything important about how it behaves. Even better, I can isolate this fragment into a partial view or a web component.
Framing the shot
Our web applications are like a small world full of (hopefully) interesting stuff, but including all the stuff in one single HTTP response is often not what the user asked or intended. Framing the shot means deciding which part of the world belongs in this request.
We can use query parameters, post variables, session state, database information, user claims/permissions, server configuration, really, anything at our disposal.
Once we decide the mechanisms of controlling the framing, we can press the shutter button: the click of a link, submitting a form, or an hx-get action that kicks off a request back to the server.
Now safely back on the server, an Umbraco surface controller gives us access to the Umbraco context, our services, the request — everything we need to decide what belongs in this particular response.
Surface Controllers are a beautiful function of Umbraco. We can return a partial view, or an entire webpage. If used in conjunction with HTMX, the library can replace a selected portion of the page or use a morphing swap to update the existing DOM while preserving as much of it as possible.
Do we need another copy of the world?
The contemporary fashion in web development, however, is to throw all this away.
A common modern architecture duplicates more and more of the application model out of the server and into the browser. We fetch data, reconstruct domain concepts client-side, maintain another state graph, and then try to keep that state synchronized with the server. Sometimes that complexity is justified. But for a surprising number of applications, the browser doesn't need another model of the world. It can ask the server to compose the next representation.
Do we need another copy of this world in the browser? Maybe not!
In a lot of ways, what I’m suggesting is where we came from. Data was stored on the server and rendered into HTML and HTML fragments. Most developers will consider this the “old way” of doing web applications (funny enough – it’s newer than the thick-client architectures we’re trying to now imitate in the browser).
In the early days of the explosion of the iOS App Store, web developers got envious of the rich interactions and capabilities of native application runtimes and borrowed ideas and concepts from those frameworks, creating capabilities we now call SPAs and PWAs. This introduced a lot of data marshalling, or as we’d call it now, a need for serialization, and a need for data contracts.
Things could be so much simpler in our modern, connected world.
Just one contract
But why should you consider following a hypermedia approach in your Umbraco projects?
In this style of application, I rarely need TypeScript for domain modeling because my strongly typed models already live on the server. There’s no need to recreate those types in another language in another environment. And if my UI consumes HTML directly, I often don't need to invent and version a separate JSON API purely to feed the frontend.
The browser merely presents the controls delivered with each response, rather than relying on a separately maintained model of our application’s data. We still have conventions to honor, but we often avoid inventing and versioning a separate JSON contract purely for our own frontend.
The contract turns to:
- The browser needs to know how to initiate an HTTP request.
- The server needs to understand how to interpret an HTTP request.
- The server needs to send back valid HTML markup.
The links that lead you to the next view require no hard coding and can indeed change at any time without needing to update client code or the contracts they depend on.
I can’t begin to explain how freeing this is.
Take your shot
Embracing the hypermedia architecture as it was originally envisioned, alongside powerful hypermedia composing tools like Umbraco, affords us as developers and content makers the ability to build rich and interesting digital photography. The magic of hypermedia is that each digital photograph contains the controls for taking the next photograph.
None of this is an argument that client-side state has no place; it's an argument that we shouldn't manufacture a second model of the world until the interaction requires one.