htmx: Build Interactive Web Apps with HTML · Hypermedia Foundations
Why htmx? Hypermedia vs JSON APIs
Build the same Like button twice, once with fetch and JSON and once with htmx, and see what each approach asks of you.
What you'll build
A Like button with a counter, built two ways: the common single-page-app way (JavaScript fetches JSON and updates the DOM) and the htmx way (the server returns the updated button as HTML). Comparing them shows the idea behind the whole course.
The idea
Since the early 2010s most interactive sites have followed one model: the server is a JSON API, and JavaScript in the browser (React, Vue, Angular, or hand-written code) turns that data into HTML. It's powerful, but every feature needs code in two places: an endpoint that returns data, and client code that knows how to render it.
htmx returns to the web's original model, hypermedia: the server sends HTML, and the browser shows it. HTML alone can only make requests from links and forms and only replace the whole page; htmx removes those limits with attributes:
hx-post="/posts/1/like": send a POST request when the button is clicked.hx-swap="outerHTML": replace the button with the HTML the server returns.
Here is the JSON version you would write without htmx:
// client.js: fetch JSON, then rebuild the button yourself
document.querySelector("#like").addEventListener("click", async (e) => {
const res = await fetch("/api/posts/1/like", { method: "POST" });
const data = await res.json(); // { liked: true, likes: 13 }
e.target.textContent = `${data.liked ? "Liked" : "Like"} · ${data.likes}`;
e.target.classList.toggle("liked", data.liked);
});
The server knows how a liked button should look, but the client must repeat that knowledge. With htmx the server renders the button once, in its template, and that's the only place it lives.
The code
<style>.like { font-size: 1.1rem; } .like.liked { color: #e11d48; }</style>
<button class="like" hx-post="/posts/1/like" hx-swap="outerHTML">Like · 12</button>
<!-- The pretend server: on a real site this part is your backend (Python, PHP, Node, Go...) -->
<script>
let likes = 12, liked = false;
server.post("/posts/1/like", () => {
liked = !liked;
likes += liked ? 1 : -1;
return `<button class="like ${liked ? "liked" : ""}" hx-post="/posts/1/like" hx-swap="outerHTML">${liked ? "Liked" : "Like"} · ${likes}</button>`;
});
</script>
How the examples in this course work: there's no real server in the page, so each example ends with a <script>
that acts as one. server.post(path, handler) (and get, put, patch, delete) defines a route; the handler
gets req with req.body, req.query, req.params and req.headers and returns HTML, or
{ status, body, headers }. The grey log shows each request. Everything above the script is exactly what you'd
write in a real page.
On a real backend the route is a few lines, for example in Flask:
@app.post("/posts/<int:post_id>/like")
def like(post_id):
post = toggle_like(post_id, current_user)
return render_template("posts/_like_button.html", post=post)
Common mistakes
- Thinking htmx is "AJAX for JSON". htmx expects HTML responses; JSON would be inserted as text.
- Rebuilding a SPA with htmx: keeping lots of state in JavaScript. Let the server own the state and render it.
- Assuming htmx means no JavaScript at all. Small client-side behaviour (menus, dialogs) is still JavaScript or plain HTML; htmx handles talking to the server.
Key points
- htmx extends HTML so any element can make any request and update any part of the page.
- The server returns HTML, so rendering logic lives in one place: your templates.
- It works with any backend that can return HTML.
Further reading: Hypermedia-driven applications · htmx documentation · Hypermedia Systems (free book)
Practice questions
What does an htmx endpoint usually return, and why does that matter?
Answer
HTML. Rendering stays in the server templates, so the client doesn't need to duplicate how the UI looks.
Name two limits of plain HTML that htmx removes.
Answer
Only links and forms can make requests, and they replace the whole page. htmx lets any element, on any event, use any HTTP method to update any part of the page.
In the Like example, what does hx-swap="outerHTML" do?
Answer
It replaces the whole button element with the button HTML the server returns, so the new count and state appear.
Open in the app to mark lessons complete, take notes and track your progress.