About this Project
This project uses a mixture of Django, Alpine.JS, and HTMX to serve as a minimalist alternative to Javascript-based frontend frameworks such as React, Vue, Angular and Svelte.
There is no build step required, no VDOM, less boilerplate, and it provides the ability to use all the power of my server-side framework (Django) while retaining the features of a modern web experience, without the bloat.
Most frontend frameworks require a lot of extra steps to play nicely with a backend web framework such as Django. Using Alpine.JS and HTMX allows you to spice up your frontend experience, while keeping the convenience and versatility of your server-side framework's capabilities. For example, this setup allows you to use Django's built-in session authentication system, which is more secure that the token-based systems that many services use, which can be vulnerable to XSS hacking attacks.
With the whiz-bang frameworks (React, Vue, etc.), a lot of what makes the backend frameworks useful is stripped away, reducing them to mere API handlers and database ORMs. Using HTMX and Alpine.js puts the muscle back into your server-side framework.
Alpine.js is designed to act as a lighter version of Vue.JS. It allows you to easily add the power of reactivity, but allows the functionality to be sprinkled into your code as desired. It works with the paradigm of a predominantly server-driven experience, not against it.
Here is an example of how an Alpine.JS component can be easily added to a page:
Here is the basic structure for the example above:
<div x-data="{ counter: 0 }"> <div> <button @click="counter++"> Click Me </button> </div> <div> Button click count: <span x-text="counter"></span> </div> </div>
HTMX is a interesting library that adds client-server interactivity to plain HTML tags. It is a tool that makes it easy to add REST-style interactions to your page, but with one difference from other such tools: It works with entire DOM elements (HTML tags), not just JSON.
This means you don't have to process the data on the client-side; you can simply return page fragments from your server. As with Alpine.JS, this gives the power back to the server-based frameworks. For example, your Django server can render all the elements with the proper data, and you simply receive the data and pop it right where it needs to go, with no Javascript-based messing around with Fetch calls.
The above example is made using a basic HTML form, with one notable difference:
<form hx-post="/get_weather/" hx-target="#demo-htmx-target">
Simply adding a couple attributes to the HTML completely changes the dynamic of the form.
The hx-post
attribute tells HTMX to send a POST request to the location specified in the attribute's value (e.g. /get_weather/)
. The POST request will contain the values of every element contained in the form, and the Django server will process them as a dictionary (key-value pair) with the form element's name
as the key, and the element's value
as the value.
The hx-target
attribute specifies which HTML element to replace when the server sends a response (e.g. the HTML element on this page that contains id="demo-htmx-target"
).
In summary, when the link is clicked, here's what happens:
hx-post
tag,
which points to a endpoint on our server that handles the request,
-
- The HTTP status code of the response
(NOTE: The server always returns a successful
200
code because HTMX may not swap out the content for certain response codes. The error code that is displayed is just a simulation of what the actual error code should be.) - - The message created on the Django server, which is made using the data from the weather API server's response.
hx-target
element with the content received from the server.Now, instead of completely refreshing the page, the client can replace fragments of the page. The server will send back the proper fragment, and it will be put in the desired location. This gives a more integrated, app-like feel, instead of refreshing the whole page and breaking the user experience.
In the above example, the server-side framework (Django) then queries the weather API server, processes the result, and returns the following HTTP response, which is placed into the #demo-htmx-target
element in the box containing the demo:
return HttpResponse( f'The temperature in {city} is ' f'{temperature} degrees {units}.')
In short, using HTMX allows us to easily extend HTML to provide a modern web experience without getting bogged down in Javascript boilerplate. It can also help us to move our business logic onto the server, which can be useful for preventing sensitive data from being exposed (such as the API key used to access the weather service, or even the name of the weather service).
Pretty much any fancy client-side stuff in this project was made with Alpine:
A lot of the logic in the Alpine components could have just as easily been implemented in Vanilla JS, but I find it's just easier to use an Alpine component so Alpine's magic is available when you need it.
Note: By default, the list of tasks is rendered server-side. This means that Django will retrieve the whole queryset and return it for each create/update/edit/update/destroy action, ie. complete server-side re-rendering (which is very inefficient). However, However, if you add a GET parameter (is_csr=1
) to the URL while viewing your tasks (e.g. /tasks/?is_csr=1
), this will enable a more efficient mode that utilizes client-side rendering. Django will only return as much data as necessary, and Alpine will iterate over the data to create your task list.
Anything that requires a network call without completely reloading the page uses HTMX (Basically anything other than clicking on links). This includes:
HTMX and Alpine.JS can be used to add interactivity and native app-like functionality to websites with a minimum of additional code. While this project could have been made without them, it showcases their usefulness, both by themselves, and also how they can be used together.