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-postwithhx-targetandhx-swapto 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-swap— how 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.
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.
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.