I work on a few projects in my spare time, and in one of them, I recently had to implement a simple version of status polling. The main application is built using Django and I don't have a lot of tolerance for working with and maintaining JS build pipelines, so I looked into htmx to solve the problem.
And I'm so thankful that I did that. Here's a quick writeup.
Background
The project in reference is txtcv, a plaintext (JSON resume based) CV building and hosting platform for software developers.
One of the project's features is cover letter generation. Given the contents of your CV
and a job description, txtcv is able to generate a cover letter for you. This is
implemented as a background job. Basically, the user clicks on a "generate cover
letter" button that sends a POST request to the server to create an empty CoverLetter
object in the database, that starts a task in the background (using a worker process)
that calls out an LLM to populate the contents of the cover letter. As a result of this
workflow, the status of the cover letter can go from "pending" to "running", to either
"successful" or "failed".
Problem
Given this workflow, it's helpful to let the user know the current status of the cover letter, so they don't need to refresh all the time to know where things stand.
In a typical backend/frontend split, this sort of thing would be implemented using an
API returning a JSON representation of the CoverLetter object. The frontend would be
set to poll the backend until the status is one of the final values (either successful
or failed).
The problem is that neither do I have (or want) an API for this purpose, nor do I use a frontend framework in this project. All the pages in the application are rendered 100% server-side using Django, and I'd like to keep things that way, as long as it's possible.
htmx + Django
I had often read about htmx – a library to add dynamic behavior to webpages without any fuss – but never really had a chance to use it in a project. This problem felt like a perfect fit for htmx.
So, I installed django-htmx in my project, added a {% htmx_script %} to the base
Django template, and got to work.
Status polling with htmx and Django
It turns out that the workflow for implementing status polling using htmx and Django is super simple. While there are multiple ways to do it, this section describes just one.
I added a Django view function in the backend that renders a cover letter template partial (all server side), given a cover letter ID. The Python code looks roughly as follows:
from django.shortcuts import get_object_or_404, render
from app.models import CoverLetter
def render_cover_letter(request, id: UUID):
cover_letter = get_object_or_404(CoverLetter, id=id)
return render(
request, "cover-letter.html", {"cover_letter": cover_letter}
)
Fairly simple. We get a cover letter ID from the URL, render the Django/HTML template, and send it back as a response to the browser.
The cover-letter.html template is where the real magic happens, so let's have a look
at that.
<div
{% if not cover_letter.has_final_status %}
hx-trigger="every 2s"
hx-get="{% url 'cover-letter' id=cover_letter.id %}"
hx-target="this"
hx-swap="outerHTML"
{% endif %}
>
<div class="flex items-start space-x-4 p-6 bg-base-100 rounded-lg border border-base-300">
...
</div>
</div>
The code inside the outermost <div> is focused on rendering the cover letter card UI,
which is not important for the purpose of this blog post. The real magic happens through
the conditional hx- attributes added to that outer <div>. Basically, we're telling
the browser that if the status of the cover letter is not one of the final values, then
fetch the /cover-letter/<id> URL every 2 seconds and replace the <div> with the
response from the server. And if the cover letter has a successful or failed status,
then all those hx- attributes are not even rendered in the Django template. All of
this logic is controlled server side, without a single line of frontend JavaScript.
Another neat thing is that whatever the "final" status of the cover letter is, the
frontend doesn't need to know or care. At the moment it's abstracted into a @property
on the CoverLetter Django model, so external code can just use that property instead
of having to know what exactly those final status values are.
Conclusion
And there we have it, status polling made trivial through htmx and Django, without a single line of frontend JS code or build pipelines.
No API endpoints to maintain, no frontend framework to set up, no JavaScript to write. Just a Django view that renders a template, a few htmx attributes, and the browser handles the rest. What could have been a complex feature turned out to be super straightforward, thanks to htmx.