Single Page Apps (SPAs) are all the rage these days: JS or TS-heavy front ends with libraries such as React, Redux and / or Angular are popular ways of creating web applications. This is essentially the approach adopted by SAFE Stack, except we write F# and transpile it into JS.
However, it wasn't always so, and years ago there was an emphasis on a more "dumb client" approach, with the server generating the HTML itself that was emitted onto the client - with UI events being sent to the server for processing.
Just looking at this two options, it's clear that there are more moving parts - unsurprisingly - with the former option. On the client, the latter approach eschews bespoke client-side technologies for a simpler approach that essentially turns the server into a view engine, rather than simply a data engine.
This latter approach was known as AJAX (Asynchronous JavaScript And XML) and was seen as an easy way to get a web app that appeared responsive without the dreaded "postback" (a full refresh of the webpage), which led to .NET technologies such as "viewstate" that were highly inefficient.
HTMX is in many ways a throwback to that approach, except it simply appears as new declarative HTML attributes to indicate what the trigger is, or which endpoint to post to. Matt gave a good example of this a few weeks ago.
Another element that is simplified is the physical application structure and build pipeline - as there is no hand-rolled JavaScript, the entire application is based on the server - meaning that there's no (immediate) requirement for JS minifiers, bundlers, transpilers etc., or a client-side project - your web server project does everything, and all your application code runs on .NET. In some ways, this is not too dissimilar from the Blazor Server programming model in terms of dependency on the server.
A working application
As part of our monthly coaching webinars, we looked this month at a web app written with HTMX. This app allows you to search for countries or world regions to report on their energy usage statistics. It supports find-as-you-type, debouncing, resorting of data based on column clicks etc., and uses the World Bank as a data source. It's quite flexible in the sense that you can change triggers relatively easily e.g. changing from click
to mouseenter
etc. and the view code is (relatively) easy to read.
What especially appeals to me about this application is that it is under 250 LOC for the entire system - views, API routing, data access, logic etc. - in a single file in a single project in a single language (100% F#, of course!). Running the application is as simple as going to the directory and calling a standard dotnet run
. This is extremely easy to reason about!
We retain the same "functional" approach on the server - endpoints are called with parameters passed as a form body; Giraffe has extension methods on the HTTP Context to make reading this easy:
/// The input search request parameters.
[<CLIMutable>]
type RawSearchRequest = {
SearchInput: string
SortColumn: string
}
let findEnergyReports next (ctx: HttpContext) = task {
let! request = ctx.BindModelAsync<RawSearchRequest>()
// ....
}
Summary
I find it interesting that, as is often the case in the world of technology and software development, the same patterns come around again and again. AJAX was considered "dead" with the rise of SPAs; now it seems to be making a comeback with applications like HEY opting for a server-only approach. There are of course trade offs - such as increased server load and increased network traffic (you're sending views of data, not just data) - but it's good to see this as an option for developers out there. Whether you might use this for a highly complex application with a rich user experience, I'm not sure. What I do know is that there are often web applications that don't necessarily require a full SPA application, and a simple HTMX application might just be everything that is required.