How I use HTMX with Go
alexedwards.net
alexedwards.net
I just shared my whole toolkit too [1], I call it the "GUS stack" -- Go, Unix, SQLite. Inspired heavily by the exe.dev "GUTS" stack [2] but with HTMX instead of Typescript.
Some other Go components in the kit...
- cockroachdb/errors for errors with stack traces
- templ for type-safe HTML templates (with htmx for reactivity and tailwindcss for CSS)
- fuego for an OpenAPI spec generated from web handlers
- sqlc for type-safe code generated from SQL
- modernc.org/sqlite for a pure Go sqlite library
- goose for SQL and Go migrations
- dbos for durable workflows in SQLite
- rod for Chrome / CDP testing and automation
Feels so productive coding, agentic coding, and building and deploying binaries with this stack.
I also add goverter for converting between sqlc models and template objects and return values. Like 50% of the boilerplate is generated and it makes type safety so nice
Why is that better than writing plain SQL like in sqlc? My main reason was being able to dynamically construct queries and reuse different bits. Plain SQL statements simply don’t compose at all, and I don’t recall sqlc giving any solution to help with this.
Hyperscript fits perfectly in the Go + HTMX stack to do DOM manipulation without having to make a server round trip or having to write a separate JS function.
I get that not many are fan of such declarative programming, but when there's already HTML file we're working with; Hyperscript feels just like an extension of it.
I have been working on a Open Payment Host[2] which handles multiple payment gateways and was able to perform complex DOM manipulation with just Hyperscript.
>the risk from Hyperscript is not greater than any other JavaScript in the application.
This is not really the case. If all of your client-side code is loaded via <script src="..."> tags from bundles on your server, and you have a CSP that blocks unsafe-eval or unsafe-inline, then you have a pretty good barrier against execution of untrusted code on your page.
A CSP is more valuable in a larger organization, where the codebase is always at risk of being modified by the organization's worst engineer.
If you're curious, and you too aren't in love with the "Modern frontend" philosophy, I would recommend trying out HTMX. Of note, the first examples of HTMX on the HTMX site are really basic, but it's much more powerful with a bit more learning.
I'm also going to hesitantly mention sveltekit. From the outside it looks like yet another JS front-end framework but having been forced to use it recently I've learned it actually has great support for the more hypertext-focused design philosophy promoted by HTMX and friends.
I'm happy that I got to experience this and I learned from it. Gotta choose your battles or something.
As for Go's html/template: I think it has one of the weirdest / most unnatural interfaces. I recently reread "A Philosophy of Software Design" and one of its key points is to keep interfaces simple and push complexity downwards, making it easier for others to use. Now why do I have to care about "cloning templates" every time I render some html template? Love the Go stdlib, but this thing feels unnecessary complex to me.
You do not have to. It's a self-sustained injury by the author. You can compile the templates once and just execute them.
The issue is that author wants to specify page titles in the main templates. If you pass titles as a context, you can split base.tmpl into BASE_BEGIN, BASE_END and use them in the final templates.
Yes, you will have to pass the title depending on the page template during the But his tactics will break anyway when you try to support different languages.
There may also be a performance benefit too - I suspect that the Clone() operation is cheaper than re-parsing the base and any necessary partial templates in each render() call, but I've never benchmarked it, so I can't assert that with certainty.
I tried to pitch htmx to my team last year and found an opportunity to create a simple webapp. One of the devs really struggled with htmx after years of muscle memory returning json and rendering in JS/JQuery. It was a lot of "I now have to do this.. how do I solve this problem?"
I think he would have struggled with any different approach whether we changed to angular, react, etc. htmx didn't really stand a chance.
The other dev, on the other hand, didn't even bother to try it.
All I heard from the team was Blazor this, blazor that.
Personally I think it's a shame. We are a small team and would be good to avoid javascript 95% of the time. We could just generate server side code/html which, imo, would create organised code base. Easy to test as well.
Any personal web projects I still use htmx and reached a decent flow with it. Completely simplifies web dev, imo.
I'm in an advisor position, and I tried very hard to mentor the team, explaining that learning this technology deepens your understanding of the browser. Whereas React etc isolates you from the actual environment you're working in - the browser.
On html/template, I like the security by default, and obviously it's built-in. But the dynamicness leaves too many open-ended questions unanswered. Templ is great, but the ergonomics leaves many things to be desired. After writing a few large production applications in it. I decided to create gsx: https://github.com/gsxhq/gsx
if your teams is less than 3 people - you've less politics to fight. & chances are people are already capable - no resume padding. so you choose what's pragmatic & that choice ends up being HTMX.
for big teams - politics takes over. even though HTMX will be beneficial - might as well go with the 'safer' political choice which doesn't cause much contention ie one of the big JS frameworks.
It is an unbelievably good tool though, and LLM's understand it very well (given some encouragement).
They can see each problems one by one but can't put it in the same picture as it's "proven technology"
For simple CRUD apps and admin dashboards, HTMX is great. But once you have lots of interconnected components, shared state, and complex interactions, managing everything quickly becomes difficult.
I originally chose HTMX because I really didn't enjoy working with React. Eventually I tried SvelteKit, and it completely changed my perspective. I still use Go for the backend, but SvelteKit in SPA mode for the frontend. It gives me a clean separation between the two while making complex UIs much easier to build and maintain.
What really sold me was that Svelte feels like a natural extension of HTML rather than a different language with JSX. State management is simple, the component model is intuitive, and the new `$state` syntax is especially nice.
Did you give it a try?
React probably scales better for huge engineering divisions, but that isn't who the GOTH stack is aiming for anyway.
I've found that to really reduce the complexity of my applications. If I really do need something more powerful with state machine interaction, then I use VanJS. But, really, I've only used that for my static sites where I need a little interaction on the front end and no back end connection.
Here's an example of using HTMZ-BE (which can easily be extended to HTMX 4.0 and Data-Star - if you need even more features).
https://github.com/jon49/Soccer
It also uses an _<action> attribute pattern on the HTML for simple, declarative front end state interaction. Makes the user's life a little nicer.
It's amazing how simple things can be if you work with it and think about, "I want simplicity." A lot of the front end frameworks add a lot of complexity, even nice frameworks like Svelte, I think because the paradigm is just wrong.
I'm feel inspired to convert some old stuff to HTMX
The reason is that htmx requires a certain amount of flexibility in the HTML generated by the backend. Eg, you need to be able to generate a certain piece of HTML markup and put a <title> tag immediately adjacent to it in some situations, and not in others. (Htmx updates the page title when it finds a <title> tag at the top level.)
This kind of flexibility is difficult with traditional string-based templating engines, but trivial with language-embedded HTML libraries.
Eg, if your backend is in JS then a tagged template literal function like https://github.com/WebReflection/uhtml-ssr
If it’s a Go backend then a library like https://www.gomponents.com/
If it’s Scala then ScalaTags. And so on, you get the picture. The point is that a language-embedded system allows you to use the full power of your language to build abstractions and components, which htmx really benefits from.
implementation: https://github.com/mastrojs/mastro/blob/main/src/core/html.t...
I did the first 90% by hand, and have done the last 10% (and README) with Claude, just to get it out there.
Opus and GPT are very good at it, it's fast to build and start, convenient to deploy and host, one binary. I like it very much.
Very good stack to iterate fast.
Moreover, htmx can only do a fraction of what datastar does - you'd need to add on something like alpinejs to be able to try to fill the gaps. Both combined are much larger than datastar, and not nearly as integrated.
Off-course these additional tools are not free [2].
[1] Rocket for WebComponents, Stellar for CSS
[2] Nothing wrong in charging but $349 is a tad too expensive for me. I will happily pay if it was in double digits.
I would add a-h/templ for templates and it has Datastar support as well.
It is staggering how Datastar is not more popular.
I think the best way to put it, when I'm working with HTMX it feels like the complexity of the codebase is growing at a 2:1 rate compared to the app itself. I always end up with some weird edge case that I can not come out of without some weird hack.
I get why people dislike Node packages, HTMX feels like it's an overcompensating response to that. But the time you save by not having to wrestle with JSON is tripled when you try to make the app actually look or feel good. It takes me 2 minutes to slap together a Mantine template [1] and tap into some of the best UI components, then I can embed the built static assets and end up with the same single Go binary.
Re: request states, handled very nicely out of the box by htmx with a little knowledge of attributes like hx-indicator and JS event handlers to handle htmx error events.
After a few attempts I learned that abstractions are important :D
Without a component builder and reversible router is indeed pretty painful (and Rails just ships with these things so you didn't have to worry about it).
The biggest benefit for me personally is that computation and data live in one place, which carries you very far. Also, machines like hypermedia: LLMs are great at using and testing hypermedia apps because they are self-contained and the cycle time is lower because you don't need to wait for JS.
And yes, no easy off the shelf component library makes starting harder.
And my own version (I learned of vuego only after): https://htmlc.sh
Not a fan of html/template and tmpl myself, because HTML should be treated as tree and not as strings, but everybody has different preferences.
For routing I now use my own library in all projects: https://github.com/dhamidi/dispatch
It's relatively easy to generate your own nowadays, I mostly go through the Ruby on Rails guides and mix and match the pieces I want for go and turn them into libraries :)
return htmx.Write(w,
&htmx.Template{
FS: htmx.FS(ui.HTMX, "parts"),
Filename: "arrows.html",
Fields: []any{thread, up},
},
&htmx.Component{
HTML: `
<div {{$count := index . 0 -}} {{- $thread := index . 1 -}}
hx-swap-oob=true
id="points-{{$thread}}"
class="points">{{$count}} points</div>`,
Fields: []any{count, thread},
},
)
[1] https://github.com/cattlecloud/webtools/tree/main/htmxThese are all existing common pain points that the new version allows us to address.
We are working on building an app builder that uses HTMX as an frontend technology, along with SQLite for the database and Bun for the backend.
Back and forth the pendulum goes.
People hate on React but it is the product of 2 decades of various experiments. It is popular because it really works.
1) Most of these people don't realize that if a component on the page is so complex you probably have usability issues.
2) React doesn't need to control the entirety of the page. You can contain your complex React components within a web page.
- Both React and Vue are all about reactivity: you have a variable, make it reactive by using something like ref() or useEffect(), and the framework will patch the DOM whenever that reactive variable changes. The idea is easy to understand but hard to implement. I agree with this podcast [1], where the author mentions that writing good React code is hard. I like Vue, but it is a bit overwhelming since it has many functions for creating reactive variables, like ref(), refs(), isRef(), unRef(), reactive(), toRefs(), computed(), etc [2].
- HTMX follows a similar idea: patching the DOM when an event occurs. Unlike React and Vue, HTMX does not rely on reactivity. Instead, it uses attributes like hx-get for requesting HTML fragments to the backend, patching the UI using those fragments. No watchers, no reactivity, just plain requests. This is (to some extend) suitable for dashboards, pagination, sorting tables, autocompletion. But if your application requires a higher level of interactivity, then React and Vue are a better option [2].
Then we have LiveView and Elixir, which I think is a better alternative/trade-off to these three tools:
- Similarly to React and Vue, LiveView has reactive variables (called assigns, you can think of it as a hashmap that holds the state of your application): every time the assigns changes, LiveView reloads the corresponding HTML fragment. This operation is efficient because LiveView knows which parts of the HTML fragment are static and which parts are dynamic. So when some variable changes, LiveView sends a tiny payload to the UI for updating the DOM.
- Also, LiveView follows an arguably simpler approach for reactivity, you only need to implement three functions: mount() to setup the initial state of the assigns, render() to prepare the HTML content, and handle_event() to handle events like button clicks or form submissions.
I think LiveView is so much easier to follow: if the assigns change, then the framework will update all the relevant HTML fragments. That's it. Also, we do not need to worry about adding third-party libraries for routing, state management, forms. Those are likely included in Phoenix.
[1] https://www.youtube.com/live/pMwTR2KeuaU?si=AU7sqxVwTv4lRhV6...
I’ve worked with Go professionally for the past 5 years, and I genuinely like the language. I like the anti-framework philosophy, the simplicity, the resistance to over-abstraction or even worse... bad abstractions, fast builds, the light native binaries, and how easy it is for a team to converge on idiomatic code.
But the moment you’re building a normal product app, even “basic CRUD” is rarely just CRUD. You need a database layer, migrations, auth, validation, maybe an OpenAPI spec, background jobs, admin flows, maybe server-side rendering, maybe GraphQL, etc.
You can end up with a large codebase full of repetitive boilerplate for an app that does not actually do much conceptually.
Maybe some people see a large codebase and feel accomplished, but just because each line of code is readable doesn't mean you know what its doing.
There is an interesting side point though. I've been blogging about Go since 2013 (generally writing similar articles to this one) and from 2013 to 2025 I think just one post made the front page of HN. In 2026, all 3 posts I've written have.
My theory is that there's now fewer people spending the time to write original content. With LLMs and AI-generated instant answers in search, the incentive to write these kind of deep-dive articles is way less than it used to be, so maybe there's both less competition and more appreciation for it? That's my working theory at the moment anyway.
Have you tried Lorca[0]? A wails that assumes you have chrome installed (as most do) so: much smaller binaries.
[0]: https://github.com/zserge/lorca
[1]: https://wails.io/