Hotwired Reactive Web Development – How LowJS Can You Go?

So my name is Josh.

I've been very fortunate in my career to be able to work with some really great companies amongst very smart people.

I've been able to change the way they think about technology and the way they deliver it.

Hopefully, I'll change your minds a little bit today too.

I think they should have called this section, the cat amongst the pigeons, given the last two talks.

So pigeons, meow, here I am.

I've had over three decades in the industry and these are some of the teams I'm working with currently or recently.

Helping them grow as teams grow the business solutions as well.

And they're all hiring.

So if you're interested, let me know.

Cookaborough in particular looking for a leader.

So when I stopped working, I decided I would try and, play with some tech.

And I wanted to, build something for a friend of mine in a company that needed to monitor many systems that they had.

And none of the monitoring tools that were out there were, good enough for them.

So this is inspired by, that it's a simple gallery of panels that show you the status of various systems that can be connected to by url, ftp, http, doesn't matter any URL.

File, even.

And obviously, healthy, ailing or unhealthy.

Lots of heuristics and configuration and, what that means to be healthy or unhealthy.

But it is a reasonably simple app.

It's meant to be a dashboard in an office and, people don't usually interact with it, but you can.

It's a Web app, so obviously HTML and CSS.

It's a modern Web app, so we've got a, few thousand lines of JavaScript.

Maybe a few orders magnitude less than that.

I got it down to 12.

I have some toolkits for what I call plumbing and, window dressing cuz I'm not great at window dressing.

And there is a reactive backend.

It's not relevant for this talk, but there is a much longer talk I did at YOW last year about this.

There is the ThymeLeaf HTML templates, which we'll see a little bit of as well cause that is relevant for the front end.

So I wanted to avoid some of what Dan was just talking about, the bulky, complex JavaScript of frameworks that are prevalent today.

But where, should I start?

Which front end embodies the state of the art of Web development these days.

As Dan said, the framework is the driver and you are the, passenger.

Is it react?

Must be right.

You guys are all here for you.

You're all here for React.

So reacts embodies the state of the art of Web development practices, right?

They're smarter than us.

Yes.

What about Vue?

Anyone?

Angular.

Clearly.

I'm old.

Ember did at one point.

So what happened?

Anything else?

See a Web applications architecture is heavily influenced by those design decisions that the framework developers make, whether implicitly or explicitly.

Sometimes we consciously accept those decisions as being in line with our Web application architecture, and that's great, but mostly we don't, we just figure they know what they're doing, they embody the best practice.

So we'll just follow.

So some years ago there was an attempt to define a set of recommendations called the Resource Oriented Client Architecture.

It's an architectural style at the front end.

It's independent of any framework or, language or tool set.

It embodies the principles of what is considered to be good Web development or Web development practice and application architecture.

So it's meant to be a living reference, one that you can implement as is, or you can compare it to other approaches and decide okay, I'm diverging from this and I know I am and that's okay.

So just in case you think, because I'm old, I'm a fuddy duddy and I'm, I don't want to embrace the modern Web.

Here are some of the attributes that I use to determine what I would want from this architecture, for this application I was building.

Subsequently, we also use this at Cookaborough in, in some of the new Web pages as well.

Would you agree that these is a pretty good list of, things that you would want in a modern Web app?

There's probably some missing you might think of, but for me it's a pretty good list.

And and one of the important ones is right at the bottom there.

They're all important, but one of the ones for me is simple.

Simplicity is really important for me cuz I get confused easily.

If you ha I happen to be doing Rich Hickey's thing here.

So that's not me, that's him.

That's Rich Hickey.

He's the inventor of closure.

It's a functional programming language on the JVM, it's Lisp-like.

He has a talk called Simple is Not Easy, and I absolutely, thoroughly recommend you look at that talk.

He points out there that simplicity is essential for reliability.

Now, this my, app it's a little homegrown app.

But it is essential.

It's that it's reliable because it's monitoring other systems.

So you need to rely on the instrumentation to an extent, yeah?

Simplicity also aids in understanding the problems that you're trying to solve.

The way that you solve them, and particularly when new people join the team in a hyper-growth company, which I've been a part of, several of, it's really important for them to understand the technical underpinnings of that solution, particularly when you are growing it.

So simp simplicity, is important there.

And I'm sure you've all joined teams where you're like, what the hell is this code?

So if you've got simple solution, then, that question doesn't get asked as much.

When it comes to finding problems in those running systems, simplicity makes it easier, more, faster and more accurate to find out what the problem is and also then when you Wanna correct it or make changes or add features, it's also better for that as well.

We've all made changes to one part of the code and then something else completely unrelated breaks, and that's cause of complexity.

So is modern Web development simple?

I don't think so.

How did we get here?

When I started.

It was Mosaic.

So we had gray backgrounds, just text.

Nobody put images or anything like that on their webpages.

If you wanted some fancy ux, you'd use marquee or blink.

And italic and bold were the avant-garde at the time.

So then a really important breakthrough came through that allowed the potential of the browser to be unleashed to make it a legitimate application delivery platform.

And that's, of course JavaScript.

And then Jesse James Garrett put together a few ideas, calls it Ajax where we can asynchronously request some data from the Server, use that to transform what's what we're seeing on the browser.

Even for a while we didn't have HTML at all.

I dunno if anyone ever did this XML and xslt is no HTML at all.

Yeah, the browser just transformed it itself.

Xml, it's too verbose and it's too unreadable and it's too restrictive and it's too flexible.

So in what I think is a spectacular show of lack of security awareness, we use JSON instead.

And what we've ended up with is this ever increasingly complex Rube Goldberg machine that works which we call single play, single page apps.

And that's where we're at now.

So what are the benefits, Josh you, seems like you're down on frameworks.

What are the benefits of them?

You're right we've got the complicated build process.

We've got an impedance between the development and the production experience.

We got rotting dependencies, dirty bloat, poor testability, and almost no control over how the app is fundamentally constructed.

So we are winning.

Is there some other way of doing it and still get modern character characteristics?

If you've ever gone to YOW or, other conferences like that, you might've seen Stefan Tilkof talking about REST and the Web native way of distributing logic.

So this is an a description of what he feels is a good way to distribute logic over the Web.

And it's a fairly simple thing.

Clients responsible for presentation and the server's responsible for everything else.

It means that you can have unknown clients use your application.

It also means that those clients can be extended optionally by code on demand.

So things like JavaScript, and this is back in the mid 2010s.

So we've come a long way since then and we have no servers anymore.

But the, logic still remains that if you want a broad set of the types of clients that you want to use your application you keep the client's eye concerns optimized to those clients.

And then there's no disparity in logic between what's in the client, which is thinking about responsiveness and then what's on the Server, which is all about security, integrity, and accuracy.

For me, this architecture removes complexity and also a large attack surface area.

And because we're in the hashtag era and the low code age, I've called it low js and.

You might have seen some things that do this sort of thing before, if you've ever used inter cooler back in the day, it's now H T M X.

It's basically HTML six, seven, or eight, whatever it's gonna do.

It extends the capability of, HTML, just the standard elements, allowing you to add simple, declarative logic to perform actions that would otherwise require JavaScript.

So pretty cool stuff.

And then there's Hotwire, and this is from the Basecamp team.

They build Basecamp obviously, and also an email app called Hey.

And this has been extracted from that application and they've contributed back to the community as a library, which is exactly what they did with Ruby on Rails back in the day as well.

So Hotwire is used for real world, apps.

As I said at Cookaborough we're using both of these in the new stuff.

There's three elements to Hotwire.

In my application I've only used Turbo.

It's, therefore responsiveness component Web components and streaming updates.

So we'll touch on that.

There are other parts of, Hotwire too, if you're interested.

So what's Hotwire?

It's a way of building the Web using HTML instead of JSON.

Really weird.

That's, where they get the name from HTML over the wire.

So the core concept is to use HTML as a data transfer format.

And you're like hang on, HTML is a markup language, surely it's not for data transfer.

And then Stefan again a few years ago said after making a very good case to use HTML as a data transfer format, that obviously that's crazy.

We wouldn't do that.

Then he reflected thinking about all the changes that happen in this part of the industry and said, maybe wait a year or two and it'll be the big thing.

It's eight years later, and here we are.

So why did I think about Hotwire?

I wanted the fast load.

I didn't want the SPA bloat.

I wanted all the process flow, the domain logic, interpolation, and data access to happen on a Server.

I wanted to be able to use anything I wanted for the backend.

I've used Kotlin in this application, but there are many examples with Ruby on Rails and, lots of other languages as well.

And I wanted it to be fast.

Like I didn't wanna sacrifice the speed and responsiveness that people think you get with single page apps.

I still wanted that and Hotwire gave that to me.

So look, this is a short talk.

I do wanna show you some code.

Let's have a look at, it.

But I'll have to keep it brief cuz it's deep.

There's resources to look at the whole thing later.

So one aspect of Hotwire is a thing called turbo frames and it's all about transclusion.

So just think about an iframe, but less security issues and, something that's built in.

So here we go.

If there's no JavaScript available, Regular page navigation takes place.

So this, is a bit of a mind blowing thing that you can use an app there's no JavaScript and it can still work.

So if I go to one of the pages in my app, the, so the index page, I get a status link, I can click on that link and the browser sends me to a new page.

What a concept, right?

And that's, that page is the gallery of systems being shown in their current health state.

That means that second page is a fully formed HTML page.

No, nothing special about it.

But if JavaScript is a, is available in the user agent, then when you navigate to the index page, you actually get a portion of the second page in transcluded in the first page.

So this is a really cool progressive enhancement that allows that HTML.

So you'd be thinking, okay, we would send JSON and we'd do DOM transformations and have it update.

But this is doing the same thing, but just with HTML.

And because this is a dashboard and there's not necessarily human interactions, usually I make that transclusion happen automatically with a little bit of code on demand.

One of my line, one of my lines of JavaScript.

So here's the index page.

Yep.

You can see that.

Okay.

There's the custom element, turbo frame within it is an anchor that you click and you go off to the second page.

If JavaScripts enables some progressive enhancement happens and, I hide the the anchor.

So when you navigate there, you don't even see it.

It just straightaway has the has the second page status gallery in it.

Here's the second page because I wanted this to work on text browsers, and it does, it works on text browsers really well.

It also scores exceptionally well on accessibility checks and it's very easy to test.

I've got a little bit of meta refresh in there.

It's important to see the current status of things.

So this page will refresh itself if if you don't have JavaScript.

And then if you do, here's the the source of the transclusion.

So on the index page, we saw the target turbo frame.

Here's the source frame, and within it, it's an unordered list with list items.

Now this is a ThymeLeaf template, so just imagine this list, these list items, being interpolated with as many systems as you are monitoring.

Another cool part of Hotwire is turbo streams.

So this works across Web sockets service Server Sent Events, or even just form submissions.

And this is all about sending fragments of HTML in a stream from the Server to the browser instead of XML And the DOM will be directly updated by the library.

You don't have to do it yourself.

There's no nothing you have to write for that to happen.

And that's all this part of the library does.

It just does those updates from the stream.

So each message is, the HTML that's gonna be used to update the page.

If you need some other event handling to happen, you can use another part of Hotwire called stimulus.

I didn't need that.

I don't have much to, do in this one.

So I've just used raw JavaScript, but they, have stimulus there to, help in that aspect as well.

And obviously I'm using a reactive collection on the backend, which I talk about in the other talk.

But any, backend system that has some sort of reactive stream that pushes data, then this will work with as well.

Because I'm doing it over the internet I prefer the Server Sent Events rather than Web sockets.

Web sockets are great in a, in a LAN, I know you all use them over the internet too, but Server Sent Events are the way to go for that for latency and performance.

Here I detect, do you have event source type in your browser?

Is turbo loaded?

If so, connect up a turbo stream to an event source from the browser and the browser will go and request that stream for you.

So another line of JavaScript.

Now.

So on the back end, I've got this reactive stream.

Every time there's any change in status from one of those systems that I'm monitoring, an element gets put on that reactive stream that then triggers a, template render.

That template render is a message that's sent over the Server Sent Event to the browser, and then there's a DOM update.

So there's this flow that happens.

Very efficient flow, very small amount of data.

Anytime there's any change to one of your systems health status, the browser will get updated.

So it's not full page refreshes or anything like that, except if you're not using JavaScript in your user agent.

Here's that template element there is telling turbo Stream this is the payload.

Yeah.

This is the content of the message.

And you can see they're a a replace action.

So there's lots of different actions prepend, append, whatever.

In this case I'm, just wanting to update the panel.

That's, for that particular system whose status has changed.

So that's a replace.

So that means that everything inside the template element will be used to replace the current thing in the OM.

So if we look at it from the browser's perspective, I've opened up the Chrome event stream debug tab.

We can see some IDs there, like all these messages look the same except for the IDs changing, and that just means there's a change in status for that particular system.

I've got some CSS in there.

We'll have a look at, you can see the, list item there and it's got some cla CSS classes that will say eventually show to the user green, amber, or red.

There's I identifiers that match up the backends I understanding of which system's being monitored to a DOM element.

Which is a nice way to to, be able to debug as well.

I can very simply work out what the back end thinks is going on from what, I see in the front end.

I'm using a fragment of ThymeLeaf template here because when I navigated to the status page or the list items that come up, that's a full page.

Yeah.

And the, messages that come across the turbo stream or the, SSE stream, are just fragments of HTML.

But I've used exactly the same template for that.

Cause I didn't want to have two different things.

So whether they're coming across a stream or being used in a full page render.

It's exactly the same template.

So let's take a quick look at that.

For me this, is still semantic, right?

This is, it's HTML, but it's still semantically rich.

Yes, it has CSS information in there for presentation, but, none of that is needed to interpret the message.

All, everything in this HTML, the markup itself is, able to convey meaning.

If this data were being read by another system and not, a browser, it would be equally as effective as XML or JSON for conveying those semantics.

So this is the idea that, look, HTML is actually, if you craft it well, it's actually pretty good at telling other systems as well, what the meaning of the payload is, so it's marking up text.

Sure.

But it also is surrounding meaning in, that content.

So for example, there's the name of an anchor with the name of the system being monitored.

If you are a human user of this, you can click through to that system.

That's an affordance I've given, but most of the time it's a dashboard.

So it's no big deal.

Obviously many systems have many instances we can tell whether it's healthy or not.

And the last time it was checked.

So what I learned doing this and what we're learning as we use this in much bigger, much more complicated system is you don't need a lot of JavaScript for those dynamic updates.

You also don't need it to process data transferred from the Server, whether you're using normal HTTP verbs or sending it across a reactive stream.

We worked out, we didn't need a, an SPA and a big bloated framework for responsive browser experiences that have some pizazz.

There are cases you do though if you absolutely must have an untrusted client perform offline state management, that's necessary.

You have to duplicate that state management domain logic and data storage to do I'm gonna say that almost all of the apps that we build do not have that requirement.

So just think about if you do have that requirement, you absolutely need to, use a, large scale framework.

If you don't, then maybe you don't need to use that.

I also worked out that you can use HTML as a effective data transfer format with a semantically rich content model that provides low friction updates to Web clients and I was able to build a Web native reactive system that was simple, fast, accessible, and had a front end using HTML CSS and a low JS toolkit.

So for me, it's absolutely clear now where the responsibility for business logic and application state is.

It all resides on the Server, which is great.

It means anytime I make a change, it's immediately taken effect in all of the types of clients that have been, that are using this, not just the Web UI.

And I don't need to trust a client to be doing the right thing in terms of security because there's nothing that the clients are doing.

So if you want to yell at me on Twitter, that's there.

If you want to be kind to me on Mastodon, I'm there.

There's a link to, to a series of articles about this goes into much greater depth.

And the code is there freely.

Use the code if you like run, it, try it out.

If you wanna learn Kotlin and co routines and you wanna get into some functional programming it's a pretty cool app for that.

It's well tested as well.

It's highly performant.

Is Dan still here?

It perf, it knocks the socks off everything in terms of performance and any questions, please hit me up.

And if you wanna, you want a job let me know too.

About

Investor / Advisor / Change maker

  • Canva - empowering everyone in the world to design anything and publish anywhere
  • Atlassian — tools to unleash the potential of every team
  • ThoughtWorks -— global technology consultancy ("Agile")
  • OzEmail - Australia's first major ISP

status.gallery

Simple information radiator

Screenshots of different dashboards showing service health.

Motivation

status.gallery

  • HTML, CSS
  • JS ~12 loc for progressive enhancement (could be less in a better world)
  • Bootstrap, Hotwire
  • SpringBoot, WebFlu ThymeLeaf
  • Kotlin Coroutines, Kotlin Flows.

A slick, lightweight "frontend"

State of the Art

React?

State of the Art

Vue?

State of the Art

Ember?

State of the Art

?

Embrace the browser, using the principles of a Resource-Oriented Client Architecture

  • Testable
  • Accessible
  • Multichannel
  • Standards compliant
  • Low latency
  • Highly concurrent
  • Asynchronous
  • On demand
  • Responsive
  • Low weight (page, data)
  • Server push
  • Simple

Background

Simple != Easy

  • Essential for reliability
  • Aids in understanding
    • Problem domain
    • Solution
    • Technical underpinnings
  • More accurate (and almost always faster) diagnosis of issues
  • More precise (and often faster) changes (fewer "unintended consequences").

Photo of Ruch Hickey, labelled "State, you're doing it wrong."

Background "The Web"

No data* from server to browser

Background "The Web"

Ajax

Background "The Web"

XML + XSLT

Background "The Web"

Illustration of a Rube Goldberg machine.

The web-native way of distributing logic

© Stefan Tilkov

Client
  • Presentation
Server(less)
  • Process Flow
  • Domain Logic
  • Data
  • Rendering, layout, styling on an unknown client
  • Logic & state machine on server
  • Client user-agent extensible via code on demand

#LowJS

Background

Some #LowJS toolkits

  • htmx htmx.org (née intercooler.is)
  • Hotwire hotwired.dev
    • Turbo - responsiveness, components, streaming updates
    • Stimulus - HTML-centric state and wiring with "a dash of custom code"
    • Strada - progressivel enhance web interactions with native replacements

Hotwire is an alternative approach to building modern web applications without using much JavaScript by sending HTML instead of JSON over the wire.

HTML over the wire

Hotwire

Wait. HTML as a data transfer format ?!?

[after making a good case to do so.]

But we all know... you would never use HTML...

Just wait a year... maybe it'll be the thing of the future.

— Stefan Tilkov, GOTO 2014

Hotwire

Benefits

  • Fast first-load pages
  • Keeps logic and template rendering on the server
  • A simpler, more productive development experience in any programming language

Hotwire: Turbo Frames

Transclusion

  • If JS is not available, a regular navigation takes place
  • Thus the transcluded resource should be well-formed HTML
  • Otherwise, a fragment of the second page is used to update a portion of the first page

index.html

<!DOCTYPE html>
<html lang="${lang}"
th: lang="${lang}"
th:with="lang=${#locale. language}"
xmins="http: //www.w3.org/1999/xhtm?" xmIns: th="http: //www. thymeleaf.org",
<head>
	<link th: replace="~{fragments/html-head]" />
	‹script src="/scripts/index.js" async defer></script>
	‹script src="/scripts/status.js" async></script>
	<title>status.gallery</title>
</head>
<body>
	<header class="bg-light bg-gradient p-3 rounded shadow-Ig">
		<div class="display-4 text-center text-shadow"›status.gallery</div>
		<div class="mx-auto small font-monospace text-center shadow-sm p-1" style="width: 8em">
			<time id="clock" datetime=""></times
		</div>
	</header>
	<turbo-frame id-"status_frame" autoscroll data-autoscroll-block="start",
	<a href="/status"' id-"status_frame_load">
	<!-- #ProgressiveEnhancement -->
	<script>document.currentScript.parentElement.hidden = true;</script>
	Status
	</a>
	</div>
	</turbo-frame>
	<link th: replace="~{fragments/footer}"/>
</body>
</html>

status.html

<!DOCTYPE html>
<html lang="${lang?"
th: lang="${lang}"
th:with="lang=${#locale. language}"
xmlns="http: //www.w.org/1999/xhtml" xmlns:th="http: //www.thymeleaf.org",
<head>
	<link th: replace="~{fragments/html-head}" />
	<title>status.gallery Status Page (unframed)</title>
	Turbo is available, index.html won't have included this head, so the meta refresh won't happen (which is good!)
	if JavaScript is disabled, Turbo won't be available, so this meta refresh will take the place of SSE
	‹meta content="3" http-equiv="refresh" name="refresh"/>
	«script src="/scripts/status.js" async></script>
</head>
<body>
	‹div class="bg-light bg-gradient p-3 rounded shadow-lg">
	<h1 class="display-1 text-center" style="text-shadow: 0 1px 0 gray"›status.gallery (unframed) </h1>
	</div>
	<turbo-frame id-"status_frame">
	<div class="box">
	<ul class="container list-group">
		<li th:class=" my-1 py-3 d-flex list-group-item list-group-item-*{alert}|"
		th: each="observee : *{observees!"
		th:id=" [observee*{id} |"
		th: object="${observee}",
		<div th: replace="~{fragments/observee-status-li :: observee-status-li}"›</div>
		</li>
	</ul>
	</div>
	</turbo-frame>
</body>
<link th: replace="~{fragments/footer}"/>
</html>

Hotwire: Turbo Streams

WebSocket, Server-Sent Events, form submission

  • HTML (fragments)
  • In lieu of XML or JSON
  • DOM directly updated (by Turbo library)
  • Only DOM updates
  • Use Stimulus for sprinkling of JS, e.g. reset form fields after response received
  • Work smoothly with Thymeleaf / Spring integration's ReactiveDataDriverContextVariable and reactive collections (e.g. a Flux or a Flow)
  • I prefer SSE.

observee-status.turbo-stream.html1

// #ProgressiveEnhancement
(window ['EventSource'] && window[ 'Turbo']) ?
	Turbo.connectStreamSource (new EventSource('/status.stream' ))
	console.warn( 'Turbo Streams over SSE not available');

shows the EventStream tab of the Chrome devtools, and describe the contents.

observee-status.turbo-stream. html

<div data-th-replace="~{fragments/observee-status-li :: observee-status-li]"></div>

observee-status-li.html

<a class="fw-bold text-dark" data-th-href="*{location}" data-th-text="*{label}">
	An Observee
</a>

Recap

  • You can use HTML as an effective data transfer format
  • with a semantically rich content model
  • providing low-friction updates to web clients
  • You can have a web-native reactive system that is
    • simple
    • fast
    • accessible
    • with a frontend using HTML, CSS, and a #LowJS toolkit.

https://status.gallery

Josh Graham @mas.to@delitescere