was a time, way back, when constructing web sites was straightforward. HTML and CSS. It felt easy. These days, Javascript frameworks are in all places. Relentless change, growing complexity. This phenomenon is known as “Javascript Fatigue” and is all about builders exhausted by chasing the most recent frameworks, construct instruments, libraries, and attempting to maintain the tempo. With HTMX, builders now have a technique to construct partaking internet functions with better simplicity and fewer burnout — and with out all of the JS trouble.
An interesting internet software like ChatGPT, in lower than 200 strains of code, pure Python and HTML. Like this one:
A fast refresher on how the Internet labored
When Tim Berners-Lee created the primary internet web page in 1990, the system he designed was principally a “read-only” system, that will result in pages linked between themselves with hyperlinks, which everyone knows as anchor tags in HTML. HTML 1.0 was subsequently counting on one single tag and provided easy navigation between pages.
About Us
The anchor tag is a hypermedia management that does the next course of:
- present the person that it is a hyperlink (clickable)
- subject a GET request to the hyperlink URL
When the server responds with a brand new web page, the browser will exchange the present web page with the brand new web page (navigation)
Then got here Internet 2.0 which launched a brand new tag, the shape tag. This tag allowed to replace ressources along with studying them through the tag. Having the ability to replace ressources meant that we might actually begin constructing internet functions. All of this with solely two controls: and .
The method when submitting a kind is kind of much like the anchor tag, besides that we will:
- select which form of request we need to carry out (GET or POST)
- connect person data like electronic mail, password, and many others. to be handed with the request
The 2 tags are the one parts, in pure HTML, that may work together with a server.
After which got here Javascript.
JavaScript was initially created so as to add easy interactions to internet pages: kind validation, information fetching, and primary animations. However with the introduction of XMLHttpRequest (later referred to as AJAX), JavaScript developed into one thing way more highly effective and sophisticated.
With Javascript, builders might now set off HTTP requests with out the 2 tags, utilizing one thing known as AJAX. AJAX permits to fetch information from the server, and although XHR can fetch any kind of information, together with uncooked HTML fragments, textual content, or XML, JSON turned the de facto information alternate format.
This implies there must be a further step the place JSON will get transformed to HTML, through a operate that renders HTML from JSON. As proven within the instance beneath, we proceed by:
- fetching JSON information from the
/api/customersendpoints (theresponse => response.json()half) - inserting this information right into a HTML templates (the
const htmlhalf) - that can then be added to the DOM (the
doc.getElementById()half)
// The JavaScript manner: JSON → HTML conversion
fetch('/api/customers')
.then(response => response.json())
.then(customers => {
const html = customers.map(person =>
`${person.identify}
`
).be a part of('');
doc.getElementById('customers').innerHTML = html;
});
This rendering entails a decent coupling between the JSON information format and the operate itself: if the JSON information format adjustments, it might break the HTML rendering operate. You already see one potential drawback right here, and this level is often a friction level between frontend and backend builders: frontend dev builds a UI based mostly on an anticipated JSON format, backend dev decides to alter the format, frontend dev must replace UI, backend dev adjustments once more, frontend dev adjustments once more, and many others.
For some motive, internet builders began placing JSON in all places and managed all the things with JS. This result in what we name Single-Web page Purposes (SPAs): not like conventional HTML 2.0, we don’t navigate between pages anymore. All of the content material stays on one web page, and the content material is up to date with JS and UI rendering. That is how frameworks like React, Angular, Vue.js work.
“The rising norm for internet growth is to construct a React single-page software, with. server rendering. The 2 key parts of this structure are one thing like:
– The principle UI is constructed & up to date in JavaScript utilizing React or one thing comparable.
– The backend is an API that that software makes requests in opposition to.
This concept has actually swept the web. It began with a couple of main widespread web sites and has crept into corners like advertising and marketing websites and blogs.”(Tom MacWright, https://macwright.com/2020/05/10/spa-fatigue)
Most present SPA architectures are “client-thick” functions the place a lot of the job happens on the client-side and the place the backend is merely an API returning JSON. This setup is understood for offering snappy and clean person experiences, however do we actually want that complexity each time?
“(…) there are additionally quite a lot of issues for which I can’t see any concrete profit to utilizing React. These are issues like blogs, shopping-cart-websites, mostly-CRUD-and-forms-websites.”
(Tom MacWright, https://macwright.com/2020/05/10/spa-fatigue)
Javascript Fatigue is actual
The “Javascript fatigue” is getting louder. It refers back to the primary drawbacks of SPA growth:
- Rising complexity: Libraries and frameworks have turn into more and more heavy and sophisticated, requiring massive groups to handle. Some opinionated frameworks additionally imply that JS builders should specialize on one tech. No Python developer each known as itself “A Tensorflow Python developer”. They’re simply Python builders, and switching from TF to Pytorch nonetheless means you may learn and use the 2.
- Tight coupling: The coupling between information APIs and the UI creates friction inside groups. Breaking adjustments happen on a regular basis, and there may be not technique to resolve this so long as groups use JSON as their alternate interface.
- Framework proliferation: The variety of frameworks retains growing, resulting in an actual feeling of “fatigue” amongst JS builders.
- Over-engineering: You don’t want JS-heavy frameworks 90% of the time. And in some instances (content-heavy apps), it’s even a foul concept.
Apart from extremely interactive/collaborative UIs, easy HTML with Multi-Web page Purposes is usually sufficient.
So what’s HTMX?
HTMX is a really light-weight JS library (14k) that provides a HTML-centric strategy to constructing dynamic internet functions. It extends HTML by permitting any aspect to make AJAX requests and replace any a part of the DOM. Not like JS frameworks which do all of the rendering on the consumer facet, the heavy lifting is completed by the server by returning HTML fragments to be inserted within the DOM. This additionally implies that should you already know templating engines and HTML, the training curve might be a lot a lot a lot simpler in comparison with studying React or Angular.
As a substitute of abandoning hypermedia for JSON APIs, HTMX makes HTML extra succesful with the next:
- Any aspect could make HTTP requests (not simply
and) - Any HTTP technique (GET, POST, PUT, DELETE, PATCH)
- Any aspect could be focused for updates
- Any occasion can set off requests (click on, submit, load, and many others.)
In truth, you may truly write your personal little GPT-like UI with HTMX and just some strains of Python!
An actual demo: a ChatGPT app with HTMX and FastAPI
For this text, we’ll construct a little bit chat with lower than 100 strains of Python and HTML. We are going to begin with quite simple demos to point out how HTMX works, then add a easy chat UI, then add a streaming functionality to our chat. To make issues much more enticing, we’ll use the Google Agent Growth Toolkit, so we will leverage brokers in our chat!
Easy HTMX demos
Let’s assume we now have an API that returns a listing of customers. We need to click on a button to fetch the information and show a listing.

The normal, JS-way:
Demo
And that is how you’ll do with HTMX.
First create your backend:
from fastapi import FastAPI, Request
from fastapi.responses import HTMLResponse
from fastapi.templating import Jinja2Templates
import requests
app = FastAPI()
templates = Jinja2Templates(listing="templates")
@app.get("/", response_class=HTMLResponse)
async def house(request: Request):
return templates.TemplateResponse("demo.html", {"request": request})
@app.get("/customers")
async def get_users():
r = requests.get("https://dummyjson.com/customers")
information = r.json()
html = ""
for row in information['users']:
html += f"{row['firstName']} {row['lastName']} n"
return HTMLResponse(html)
After which the HTML:
Demo
And also you get precisely the identical outcome! What occurred simply right here? Have a look at the aspect. We see 3 attributes beginning with hx-. What are they right here for?
hx-get: Clicking on this button will set off a GET request to the/customersendpointhx-target: It tells the browser to interchange the content material of the aspect which has theusersListid with the HTML information acquired from the serverhx-swap: It tells the browser to insert the HTML contained in the goal aspect
With that, you already know tips on how to use HTMX. The gorgeous factor about this fashion of doing is that should you determine altering your HTML, it received’t break something in your web page.
There are, of programs, benefits and disadvantages in utilizing HTMX. However as a Python developer, it feels very good to mess around with my FastAPI backend and never fear quite a bit about rendering HTML. Simply add Jinja templates, a dose of Tailwind CSS, and also you’re good to go!
Our first chat with HTMX and FastAPI
So now could be the second when issues are getting severe. What we’ll do, as a primary step, is construct a dumb chatbot that can take the customers question, and spit it backwards. For that we are going to construct a web page with:
- a listing of messages
- a textarea for the person’s enter
And guess what, HTMX will care for sending/receiving the messages! That is what the outcome will appear like:

Overview
The movement is the next:
- Person inputs a question in a textarea
- This textarea is wrapped in a kind, which is able to ship a POST request to the server with the
questionparameter. - The backend receives the request, does one thing with the
question(in actual life, we will use a LLM to reply the question). In our case, for demo functions, we’ll simply reply by reverting the question letter by letter. - The backend wraps the response in an HTMLResponse (not JSON!)
- In our kind, HTMX tells the browser the place to insert the response, as proven within the
hx-target, and tips on how to swap it with the present DOM
And that is all. So let’s start!
Backend
We are going to outline a /ship route that anticipate a question string from the frontend, inverts it, and sends it again in a
from fastapi import FastAPI, Request, Type
from fastapi.templating import Jinja2Templates
from fastapi.responses import HTMLResponse
import asyncio
import time
app = FastAPI()
templates = Jinja2Templates("templates")
@app.get("/")
async def root(request: Request):
return templates.TemplateResponse(request, "simple_chat_sync.html")
@app.publish("/ship")
async def send_message(request: Request, question: str=Type(...)):
message = "".be a part of(listing(question)[::-1])
html = f""
return HTMLResponse(html)
Frontend
On the frontend facet, we outline a easy HTML web page utilizing Tailwind CSS and HTMX:
[email protected]&show=swap" rel="stylesheet">
// ZeChat
Let’s have a more in-depth look the tag. This tag has a number of attributes, so let’s take a minute to overview them:
hx-post="/ship": It would make a POST request to the/shipendpoint.hx-trigger="click on from:#submitButton": This implies the request might be triggered when thesubmitButtonis clickedhx-target="#chat": This tells the browser the place to place the HTML response. In that case, we wish the response to be appended to the listing.hx-swap="beforeend": The hx-target tells the place to place the content material, the hx-swap tells HOW. In that case, we wish the content material to be added earlier than the tip (so after the final baby)
The hx-on::before-request is a little bit bit extra advanced, however could be defined simply. It mainly occurs between the clicking and the second the request is distributed. It would add the person enter the underside of the listing, and clear the person enter. This manner, we get a quick person expertise!
A Higher chat (streaming + LLM)
What we constructed is a quite simple but useful chat, nonetheless if we need to plug a LLM, we'd have some instances when the response from the server takes a very long time. The way in which our present chat is constructed is synchronous, which means nothing will occur till the LLM is completed writing. Not an important person expertise.
What we want now could be streaming, and an actual LLM to have a dialog with. And this is Part 2.

