BuildBot

HTML Over the Wire

Hypermedia, not JSON

Lesson 1 of 5

What you'll learn

  • Understand the hypermedia model: the server responds with HTML, not JSON
  • Use hx-get/hx-post with hx-target and hx-swap to update part of a page
  • Avoid the #1 mistake: returning JSON or a full page from a fragment handler

In a SPA, the server speaks JSON and the client rebuilds the DOM from it. A hypermedia app deletes that translation layer: the server responds with HTML, and htmx swaps it into the page. Your Go handler renders the exact markup the user will see — there is no client-side data model to keep in sync, because there is no client-side data model.

htmx is one script tag plus attributes. Here's the page the initial GET returns:

<script src="/static/htmx.min.js"></script>

<form hx-post="/todos" hx-target="#list" hx-swap="beforeend">
  <input name="title" placeholder="What needs doing?">
  <button>Add</button>
</form>
<ul id="list">
  <li>Learn hypermedia</li>
</ul>

Four attributes carry most of htmx:

  • hx-get / hx-post — the verb and URL to hit when the element triggers (a form triggers on submit, a button on click).
  • hx-target — a CSS selector for where the response lands. Default: the element that made the request.
  • hx-swaphow it lands: innerHTML (default), outerHTML, beforeend, afterbegin, delete, and friends.

Submitting that form POSTs title as regular form data — and the handler answers with one <li>:

mux.HandleFunc("POST /todos", func(w http.ResponseWriter, r *http.Request) {
    todo := store.Add(r.FormValue("title"))
    // The entire response body: one list item. Not JSON. Not a page.
    tmpl.ExecuteTemplate(w, "todo-item", todo)
})

htmx appends it inside #list (beforeend), and the page is up to date. Same net/http, same Go 1.22 ServeMux patterns as the previous course — only the response body changed dialect.

The #1 mistake: fragments, not JSON, not pages

Exactly one kind of route returns a full page: the initial browser GET. Every htmx-triggered handler returns only the HTML that changes. Return JSON and htmx will swap in raw text like {"title":"..."}; return a full page and you'll nest a page (nav, styles and all) inside your own <ul>. If you see a page-within-a-page, a fragment handler returned too much.

The challenge models the whole loop in JS: a fake server whose routes return HTML-fragment strings, and a tiny hx-swap simulator that patches a "DOM" string.

The hypermedia loop (JS model)

Run it. Routes return HTML fragments; the hx() simulator swaps each one into the DOM string per the hx-swap strategy. Try changing beforeend to innerHTML and watch the existing item vanish.

Loading editor…
Knowledge check

A form has hx-post="/todos" hx-target="#list" hx-swap="beforeend". What should the handler return?

Next: those fragments shouldn't be hand-built strings — html/template renders full pages and fragments from one set of partials, with XSS escaping for free.

Saved on this device. Sign in to sync your progress everywhere.