Gastro

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.

Colette

If you can read, you can cook. If you know Go, you can use Gastro.

Linguini

I don't really know what I'm doing, but the file-based routing makes it easy.

Remy

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