JavaScript fatigue: HTML is all you need to build ChatGpt – Part 1

There was a time, long ago, when building websites was easy. HTML and CSS. It felt easy. Today, JavaScript frameworks are everywhere. Constant change, increasing complexity. This thing is called “JavaScript fatigue” and it's about developers who are tired of chasing the latest frameworks, building tools, libraries and trying to keep up. With HTML, developers now have a way to build engaging applications with a simple and intuitive interface – and without all the JS HABLE.
An engaging web application like chatgpt, in less than 200 lines of code, pure Python and HTML. Like this:
A quick overview of how the web works
When Tim Berners-Lee created the first Web page in 1990, the system was not designed as a “read-only system”, which would lead to linked pages that were connected to HTML Anchor tags. Therefore, they rely on one single tag and offer easy navigation between pages.
About Us
The anchor tag is a hypermedia control that does the following:
- Show the user that this is a (clickable) link
- remove the hyperlink url request
When the server responds with a new page, the browser will replace the current page with a new page (navigation).
Then came web 2.0 which introduced a new tag, the form tag. This tag is allowed to update resources in addition to reading them with tags. Being able to review the guts means we can really start building web applications. All this with just two controls: and .
The process of submitting a form is similar to the anchor tag, except that we can:
- Choose what type of request we want to make (receiving or posting)
- Paste user information such as email, password, etc. Transfer by request
Only two tags are the only elements, in pure HTML, that can interact with the server.
Then came Javascript.
JavaScript was originally developed to add simple interactions to Web pages: Form validation, data retrieval and basic graphics. But with the introduction of XMLHTTRTREETERPEETERPEETERPEEDPEED (later known as Ajax), JavaScript evolved into something more powerful and sophisticated.
With JavaScript, developers were able to trigger HTTP requests without two tags, using something called Ajax. AJAX allows downloading data from the server, and although XHR can download any type of data, including pieces of raw HTML, text, or XML, JSON BABSO DE FORTO SOKUNGENCE EXCHEPPLANCE.
This means that there needs to be an extra step where Json is converted to HTML, with a function that combines HTML from JSON. As shown in the example below, we proceed with:
- Downloading json data from
/api/usersEndpoints (Theresponse => response.json()part) - Entering this data into HTML templates (The
const htmlpart) - that will be added to Dom (The
document.getElementById()part)
// The JavaScript way: JSON → HTML conversion
fetch('/api/users')
.then(response => response.json())
.then(users => {
const html = users.map(user =>
`${user.name}
`
).join('');
document.getElementById('users').innerHTML = html;
});
This interpretation involves a tight coupling between the JSON data format and the function itself: If the JSON data format changes, it can break the HTML rendering function. You already see one potential problem here, and this point is often a point of conflict between frontlend and bactend dev who decide to update the UI, Prevent Dev needs to update the UI, Prevent Dev changes again, etc.
For some reason, Web developers started putting JSON everywhere and handling everything with JS. This leads to what we call a single page application (SPAS): Unlike traditional HTML 2.0, we no longer jump between pages. All the content stays on one page, and the content is updated with JS and UI translations. How is this framework similar to making work, angular, vue.Js work.
“The emerging Norm of Web development is to create a Round-Page system, provided. Two important features of this system are something like:
– The main UI is built and updated in JavaScript using react or something similar.
– Backlend is an API that this program makes requests against.
This idea has really taken the Internet by storm. It started with a few popular popular websites and moved into niches like marketplaces and blogs. “(Tom Macwight,
Most current Spa architectures are “thick-client applications” where most of the work comes from the client side and where the backend is just APS that returns Json. This setup is known to provide a snappy and smooth user experience, but do we really need that complexity all the time?
“(…) There are many problems that I can not see any concrete benefit of using reaction. Those things like blog, shops-websites, most of the websites.”
(Tom Macwight,
JavaScript fatigue is a real thing
“Javascript” fatigue is increasing. It refers to the main issues of SPA development:
- Increasingly: Libraries and frameworks have become more and more complex, requiring large teams to manage. Some ideological frameworks dictate and imply that JS developers should focus on certain technologies. There is no Python developer at all who called themselves a Python Python developer “. They are just Python developers, and they changed from TF to Pytorch and now it means that you can learn and use two.
- A tight fit: Integration between data APIs and UI creates tension within teams. Changes happen every day, and there is no way to solve this as long as teams use json as their exchange interaction.
- A frame of amber: The number of frameworks keeps increasing, leading to a real feeling of “fatigue” among JS developers.
- Advanced engineering: You don't need JS-Hard Framers Program 90% of the time. And in some cases (content heavy apps), it's a bad idea.
Without a highly interactive/collaborative UI, simple HTML with multi-page applications is sometimes sufficient.
So what is htmx?
HTMX is a Lightweight JS Library (14K) that provides the HTML-Centur approach to building dynamic web applications. It overrides HTML by allowing any element to make AJAX requests and refresh any element of the DOM. Unlike a JS framework that does all the rendering on the client side, the heavy lifting is done by the server by retrieving HTML fragments to be inserted into the Dom. This also means that if you already know templating engines and HTML, the learning curve will be much easier compared to learning react or angular.
Instead of abandoning the hypermedia JSON API, HTMX makes HTML more capable as follows:
- Any object can make HTTP requests (not just
and) - Any HTTP method (GET, POST, PUT, DELETE, PATCH)
- Any element can be targeted for updates
- Any event can trigger requests (click, submit, load, etc.)
In fact, you can actually write your own little GPT-like UI with HTMX and just a few lines of Python!
A real demo: a ChatGPT app with HTMX and FastAPI
For this article, we will build a little chat with less than 100 lines of Python and HTML. We will start with very simple demos to show how HTMX works, then add a simple chat UI, then add a streaming capability to our chat. To make things even more attractive, we will use the Google Agent Development Toolkit, so we can leverage agents in our chat!
Simple HTMX demos
Let’s assume we have an API that returns a list of users. We want to click a button to fetch the data and display a list.

The traditional, JS-way:
Demo
And this is how you will do it with HTML.
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(directory="templates")
@app.get("/", response_class=HTMLResponse)
async def home(request: Request):
return templates.TemplateResponse("demo.html", {"request": request})
@app.get("/users")
async def get_users():
r = requests.get("
data = r.json()
html = ""
for row in data['users']:
html += f"{row['firstName']} {row['lastName']} n"
return HTMLResponse(html)
And then the HTML:
Demo
And you get exactly the same result! What's going on here? Look thing. We see 3 attributes starting with hx-. What is here?
hx-get: Clicking on this button will start the application/usersthe conclusionhx-target: Tells the browser to replace the content of the element withusersListid with the HTML data received from the serverhx-swap: Tells the browser to include the HTML inside the target element
With that, you already know how to use HTML. The good thing about this approach is that if you decide to change your HTML, it won't break anything on your page.
There are, of course, advantages and disadvantages to using HTML. But as a Python developer, it feels a lot more fun to play around with my Fastapi Backends and not worry so much about rendering HTML. Just add Jinja templates, tirewind CSS, and you're ready to go!
Our first discussion on HTML and Fastapi
So now is the time when things get ugly. What we're going to do, as a first step, is build a dumb chatbot that will take users' questions, and spit them back. So we will create a page with:
- List of messages
- user input text
And guess what, HTMX will take care of sending/receiving messages! This is what the result will look like:

Processing
The flow of the following:
- A user who enters a query into a document
- This document is wrapped in a form, which will send a mail request to the server via
queryparameter. - The backend receives the request, does something with it
query(In real life, we can use LLM to answer the question). For us, for demo purposes, we'll just go back and answer the question book by book. - The backend wraps the response in HTMLResponse (not JSON!)
- In our method, HTMX tells the browser where to insert the response, as shown in
hx-targetand how to replace it with the current DOM
And all this is. So let's get started!
Kill back
We will explain a /send route that expects a query the thread from the front, it goes in, and you send it back to
from fastapi import FastAPI, Request, Form
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.post("/send")
async def send_message(request: Request, query: str=Form(...)):
message = "".join(list(query)[::-1])
html = f""
return HTMLResponse(html)
The previous layer
On the frontend side, we define a simple HTML page using Tailwind CSS and HTMX:
[email protected]&display=swap" rel="stylesheet">
// ZeChat
Let's take a closer look tag. This tag has several attributes, so let’s take a minute to review them:
hx-post="/send": Will make a postal request to/sendthe conclusion.hx-trigger="click from:#submitButton": This means that the request will be created theresubmitButtonis clickedhx-target="#chat": This tells the browser where to place the HTML response. If so, we want the answer to be evaluated in the array.hx-swap="beforeend": HX-Target tells where to put content, HX-Swapp tells how. In that case, we want the content to be inserted before the end (so after the last child)
This page hx-on::before-request it's a bit complicated, but it can be easily explained. It basically happens between the click and the moment the request is sent. It will add the user input to the bottom of the list, then delete the user input. This way, we get a snappy user experience!
Better discussion (broadcasting + LLM)
What we report is a very simple but effective conversation, however if we want to connect LLM, we can have some times when the response from the server takes a long time. The way our current Chat is built is synchronized, meaning nothing will happen until the LLM is finished writing. Not a good user experience.
What we need now is streaming, and a real LLM to have a conversation with. And this is part 2.



