Guestbook (HTMX)
The same guestbook from the Datastar example, but powered by HTMX (opens in new tab) instead. HTMX uses plain HTML fragment responses rather than SSE, making the server handlers simpler at the cost of some reactivity features.
If you can read, you can cook. If you know Go, you can use Gastro.
I don't really know what I'm doing, but the file-based routing makes it easy.
Anyone can cook... and anyone can build web apps with Gastro!
How It Works
HTMX extends HTML with attributes that issue AJAX requests and swap HTML fragments into the page. Unlike Datastar (which uses SSE), HTMX handlers return plain HTML responses.
Search-as-you-type
The hx-trigger attribute fires on input changes with a 300ms debounce. HTMX sends a GET request with the input value and swaps the response into the target:
<input
hx-get="/api/htmx/search"
hx-trigger="input changed delay:300ms, search"
hx-target="#htmx-guestbook-list"
name="q"
>
Form Submission
The form uses hx-post instead of the traditional method="POST". HTMX serializes the form data and swaps the response HTML into the target:
<form hx-post="/api/htmx/add"
hx-target="#htmx-guestbook-list"
hx-swap="outerHTML">
Server Handler (HTMX vs Datastar)
The key difference: HTMX handlers write plain HTML to the response. No SSE setup, no event framing — just render and write:
// HTMX: plain HTML response
func handleHtmxSearch(w http.ResponseWriter, r *http.Request) {
query := r.URL.Query().Get("q")
entries := demo.SearchEntries(query)
html, _ := renderEntryListHTML(entries)
w.Header().Set("Content-Type", "text/html")
fmt.Fprint(w, html)
}
// Datastar: SSE response with patch events
func handleDsSearch(w http.ResponseWriter, r *http.Request) {
query := r.URL.Query().Get("search")
entries := demo.SearchEntries(query)
html, _ := renderEntryListHTML(entries)
sse := datastar.NewSSE(w, r)
sse.PatchElements(html)
}
Inline Editing
Each entry has an edit button with hx-get that fetches the edit form and hx-swap="outerHTML" that replaces the entry in place. Saving works the same way:
<!-- Trigger edit -->
<button hx-get="/api/htmx/edit/1"
hx-target="#entry-1"
hx-swap="outerHTML">Edit</button>
<!-- Save edit -->
<button hx-post="/api/htmx/save/1"
hx-target="#entry-1"
hx-swap="outerHTML"
hx-include="#edit-message-1">Save</button>
Loading States
HTMX has a built-in loading indicator pattern. Elements with the htmx-indicator class are hidden by default and shown while a request is in flight:
<input hx-get="/api/htmx/search"
hx-indicator="#search-spinner">
<div id="search-spinner" class="htmx-indicator">
<div class="spinner"></div>
</div>
When to Choose HTMX vs Datastar
| HTMX | Datastar | |
|---|---|---|
| Response format | Plain HTML fragments | SSE events with DOM patches |
| Multiple updates | Requires hx-swap-oob for out-of-band swaps |
Multiple PatchElements calls in one response |
| Client-side state | Minimal (mostly DOM-driven) | Reactive signals (data-signals, data-bind) |
| Server complexity | Simpler (standard HTTP responses) | SSE setup required |
| Real-time updates | Requires SSE extension or WebSocket | SSE is the default transport |
| Ecosystem | Large, mature, extensive extensions | Newer, growing quickly |