DEV Community

Cover image for Django, HTMX and Alpine.JS: A Match Made In Heaven
arcanemachine
arcanemachine

Posted on • Edited on

Django, HTMX and Alpine.JS: A Match Made In Heaven

If you want to see HTMX and Alpine.JS in action together, click here to see a basic project I put together while using the two.

Contact Moen Digital Services if you need help bringing your digital ideas to life.

Table Of Contents

  1. Introduction
  2. What is HTMX?
  3. What is Alpine.JS?
  4. Why Use HTMX and Alpine.JS Together?
  5. How Well Do HTMX and Alpine.JS Work Together?
  6. Notes on Using HTMX with Django
  7. Conclusion

Introduction

I've been trying to find a way to take the features of old-school server-driven frameworks such as Django, and find a way to provide a modern web experience such as the ones provided by the frontend Javascript frameworks such as React, Vue, Angular, and Svelte (among others). Namely, I want to be able to update elements of the page while still preserving the state of the web application at any given time. I also want to benefit from the ease with which these frameworks allow you to add functionality to a web app without all the boilerplate.

Django (and other server-side frameworks) can be made to play nice with these frontend frameworks, but this involves the use of a REST framework that restricts the usefulness of Django, particularly its ability to render HTML templates, and also its session management capabilities, which make it easy to manage user authentication and sessions.

Although Django works very well for me, it still feels as though it was designed for a different era. By default, it produces plain HTML templates which contain links which, when clicked, will blank out the screen and fill it with a newly-fetched template. While there is no rule that it must be used that way, this is how I have come to understand the flow of Django. There is JSONResponse, but this requires the data to be processed, DOM elements manipulated, etc.

What if there was a way to preserve the bulk of a given page's content, but easily swap certain parts of the page with new data?

This is where HTMX comes in.

What is HTMX?

HTMX is described by its creator as "high power tools for HTML".

It functions a lot like an ajax or fetch() call, but with a couple significant differences:

  • HTMX allows for GET/POST/PUT/DELETE/etc. calls from any element, not just <form> and <a>.
  • HTMX allows you to specify a DOM target to replace with the newly-fetched data.
  • Instead of fetching JSON data, HTMX (by default) expects to receive HTML content.

Using these principles, HTMX helps to put some of the power back into your server-side framework (e.g. Django), since you are no longer required to process the incoming data. Just create a view that acts as a pseudo-endpoint, render the data into a template, and insert that into your target DOM element.

Here is an example of how HTMX is used:

<button hx-post="/clicked"
        hx-swap="outerHTML"
        hx-target="#your-target-element">
  Click Me
</button>

<div id="#your-target-element">
  This content will be replaced.
</div>
Enter fullscreen mode Exit fullscreen mode
  • hx-post allows you to specify the request method used (GET/POST/PUT/DELETE/etc.)
  • hx-target specifies the CSS selector of the element whose content will be replaced when the server returns a response.
  • hx-swap describes how the incoming data will be placed in relation to the target element. The new data can be appended/prepended to the old data, it can replace the old data entirely, etc.

As you can see, the addition of a few HTML attributes allows your HTML to become a lot more capable of producing an app-like experience.

Although my demo project only uses HTMX before basic fetching and swapping, HTMX is a rather versatile library that can perform quite a few tasks. You can see some example's of HTMX's power on its examples page.

What is Alpine.JS?

In short, Alpine.JS is a lighter version of Vue.JS. It is a small library that allows you to easily add reactivity to your web apps (ie. change in state is immediately reflected in the application), as well as some other quality-of-life features. Like HTMX, it is a Javascript library that is used by adding inline HTML attributes.

Here is an example of a basic Alpine component:

<div x-data="myComponent()">
  <div>
    The value is <span x-text="myValue"></span>.
  </div>
</div>

<script>
function myComponent() {
  return {
    myValue: 5
  }
}

// The component will be rendered as "The value is 5."

</script>

Enter fullscreen mode Exit fullscreen mode

Alpine.JS can be used for transitions, conditional rendering, and event handling, iterate over arrays... It really is Vue's little sibling. Even the syntax is nearly identical: Compare the HTML syntax to create an event listener: v-on:click (Vue) vs x-on:click (Alpine). Alpine even borrows the same shorthand syntax (they both use @click as shorthand to create a click event listener).

So why use Alpine instead of just using Vue? Vue uses a virtual DOM (aka VDOM) to more efficiently manage the application state before updating the actual DOM. Anything that directly manipulates the DOM (e.g. jQuery) may cause problems with Vue because it uses a VDOM, which gets tripped up when something is manipulating the DOM directly without the VDOM knowing about it. Unlike Vue, Alpine does not use a VDOM, but instead manipulates the DOM directly. This should mean that any HTML templates fetched by HTMX should not interfere with Alpine's functionality as they would with a framework that uses a VDOM.

Note that Vue will generally beat Alpine in benchmarks.

Like the other frameworks, Alpine has a browser extension that allows you to detect, inspect, edit, and debug Alpine.js data and components. It is called Alpine.js devtools, and it is available for both Chrome and Firefox

Alpine also has a third-party state-management layer called Spruce that provides a way for components to communicate with each other, and acts as a single source of truth for your app's data. It is like React Redux or Vuex.

Update: Alpine v3 now has a built-in state management system.

Why Use HTMX and Alpine.JS Together?

For my uses, HTMX does the work of swapping page elements, while Alpine is used for things like toggling navbars and modals, setting key event listeners to close the modals, and adding transition effects to both. I have found that HTMX does the bulk of the work when adding an SPA-like feel to a web app, while Alpine helps to improve the flow of the UI and reduce the amount of Javascript I need to write to do common tasks. Alpine also allows you to set event listeners on the window object, which is handy, for example, to use the Escape key to close a modal no matter what element is highlighted (the modal is also conditionally rendered using Alpine's x-if attribute, so the listener is only enabled when the modal is active).

How Well Do HTMX and Alpine.JS Work Together?

Overall, I have found the two to work very well together. I have been able to easily work with both HTMX and Alpine.

I did run into a few problems with the two libraries as I was creating my demo project, but I was unable to duplicate them when I was doing this writeup. (If you have run into any issues mixing the two, please share your experiences in the comments section.)

For client-side form validation, I found that HTMX fires a few events related to form validation, but I found it easier just to handle the submission using Javascript + Alpine, and then use HTMX's Javascript API to call it after doing my validation. For example:

<form x-data="myForm()"
      @submit.prevent="submitForm">
  <input type="text" id="my-text" name="mytext">
  <input type="submit">
</form>

<div id="form-result"></div>

<script>
function myForm() {
  return {
    submitForm() {

      // get the value of #my-text
      let myText = document.querySelector(#my-text).value;

      // if myText is empty, do not continue
      if (!myText) {
        console.log('Something went wrong.');
        return false;
      } else {

        // submit the form using HTMX
        htmx.ajax(
          'POST',
          'https://your.server/form-url/', {
            target: '#form-result',
            values: { text: myText }
        });

      }
    }
  }
}
</script>
Enter fullscreen mode Exit fullscreen mode

Notes on Using HTMX with Django

HTMX and Django make a great pair. You can render a response using a template with Django's render function, or return an HttpResponse with a string that bears a striking resemblance to JSX:

from django.http import HttpResponse

def my_view(request, your_name):
    return HttpResponse(f"""
        <div>
          Hello, {your_name}!
        </div>
    """)
Enter fullscreen mode Exit fullscreen mode

You can return scripts:

from django.http import HttpResponse

def my_view(request, your_name):
    return HttpResponse(f"""
        <script>
          // this will execute immediately
          console.log("Hello, {your_name}!");
        </script>
    """)
Enter fullscreen mode Exit fullscreen mode

You can also add status codes to your HTTP responses:

def my_view(request, your_name):
    if not your_name.isalpha():
        response = HttpResponse("That's not a real name!")
        response.status_code = 400  # bad request
        return response
    return HttpResponse(f"""
        <div>
          Hello, {your_name}!
        </div>
    """)  # HttpResponse returns status code 200 by default
Enter fullscreen mode Exit fullscreen mode

Note that HTMX doesn't change the content if a 302 response is returned, so you can return 302 if you don't want the content to change:

def my_view(request, your_name):
    if not your_name.isalpha():
        response = HttpResponse()
        response.status_code = 302
        return response
    return HttpResponse(f"""
        <div>
          Hello, {your_name}!
        </div>
    """)
Enter fullscreen mode Exit fullscreen mode

Adding a CSRF Token Header to Your HTMX Requests

Because Django forms (and any other view that accepts a non-GET method) require a CSRF token, the following script must be included to the bottom of your body so that HTMX can automatically add the incoming CSRF token to its header so that Django will accept any non-GET requests from HTMX (credit to Matt Layman):

<script>
  document.body.addEventListener(
    'htmx:configRequest', (event) => {
      event.detail.headers['X-CSRFToken'] = '{{ csrf_token }}';
  })
</script>
Enter fullscreen mode Exit fullscreen mode

Conclusion

HTMX and Alpine.JS work very well together and can let you achieve an immersive, fluid web experience. Thanks to HTMX, you can easily update fragments of a page instead of having to completely reload the page. With Alpine.JS, you get reactivity and other quality-of-life additions to your frontend experience. You also get to keep your DOM, and are not subject to the restrictions placed upon you by other frontend frameworks.

Using these two libraries also allows you to keep your server-side framework and all its features, especially templating and session authentication.

I will keep working with HTMX and Alpine.JS and hope that they will continue to work as well for me in the future as they have so far.

Check out my demo project showing HTMX and Alpine.JS in action together.

Top comments (13)

Collapse
 
adamghill profile image
Adam Hill

Nice write up! One small package to make integrating Django with HTMX a little easier is pypi.org/project/django-htmx/.

Collapse
 
ericlecodeur profile image
Eric Le Codeur

Handy... Thanks for sharing.

Collapse
 
souksyp profile image
Souk Syp.

Htmx deserves more post like this. If you come from backend, it just makes your life easier.

Collapse
 
ericlecodeur profile image
Eric Le Codeur

Great Post. I use HTMX and Alpine.js on almost all my Django project for the last year. HTMX deserve lot more publicity because they are awesome!

Collapse
 
preslavrachev profile image
Preslav Rachev

Can you also listen for HTMX events using Alpine? Something like @htmx:responseError="... handle the error on the client" is what I am looking for, but apprently, this won't work.

Collapse
 
nicholas_moen profile image
arcanemachine

Finally dug into this a bit. It appears that htmx:responseError is not fired on the element. However, htmx:error does fire, so I was able to do something like the following:

@htmx:error="(evt) => myHtmxErrorFunction(evt)"

This generic error event is currently undocumented, and I need to dig a little more to find out what events are dispatched to the element.

I think HTMX will need a PR or two in order to get things to make sense. htmx:sendError and htmx:responseError should definitely be fired on the elemtn.

Collapse
 
nicholas_moen profile image
arcanemachine

Off the top of my head, I cannot currently remember. Try asking in reddit.com/r/htmx, the creator is pretty active there.

Collapse
 
jwing8 profile image
jwing8

I am also trying to do this but cannot get it to work

Collapse
 
guzmanojero profile image
guzmanojero

How do you compare this to Django Unicorn? django-unicorn.com/
Nice article, thanks

Collapse
 
nicholas_moen profile image
arcanemachine

I haven’t used them together… This way, I’m not tied to a specific platform.

Collapse
 
jiraiyasennin profile image
Dostow**->

:-O I've just checked it, thanks for the info!!
(~˘▾˘)~

Collapse
 
mohammadekhosravi profile image
Mohammad

This is interesting.

Collapse
 
jiraiyasennin profile image
Dostow**->

Amazing Article!! :D Thanks for sharing!