Part 1, Chapter 1

Over the last decade, the most popular way to create web applications was to completely separate the frontend from the backend and use RESTFul APIs coupled with a heavy frontend JavaScript framework to create a Single-page Application (SPA).

This can work great when you have separate teams for the frontend and backend. However, this is not ideal for indie hackers who must develop both of them.

Fortunately, there's been an increase in interest and creation of lighter frameworks that support the one-man-band idea. Among the more popular are HTMX and Alpine.js on the JavaScript side and Tailwind CSS for the CSS. With each of those, the code can be written directly within the HTML, thus speeding up the development process.


HTMX is a library that allows you to access modern browser features directly from HTML, without having to use JavaScript. Plus, you can still create a a SPA-like feeling since attributes like hx-post and hx-swap update only portions of the DOM.


  1. It's lightweight (~12kB min.gz)
  2. Server-side rendering is better for SEO
  3. Gradual learning curve
  4. No JavaScript knowledge is required
  5. Any HTML element can issue an HTTP request
  6. Any DOM event can trigger a request
  7. Besides the GET and POST (supported by default), you can also use PUT, PATCH, or DELETE
  8. Any HTML element (not just the entire window) can be the target for an update by a request


  1. Relatively new, so its community is small
  2. It's lightweight -- although you can do a lot with such a small library if you need a full-scale client-side heavy app, HTMX won't be able to replace React or such

An example of what HTMX looks like:

<div id="parent-div">
   <button hx-post="/clicked"
       Click Me!

All HTMX attributes are prefixed with hx-. hx-trigger decides which event (click in the example) triggers the POST request to the /clicked URL (hx-post="/clicked"). The element set with hx-target (element with ID parent-div) is then entirely swapped (hx-swap="outerHTML") with the content of the response.

Tailwind CSS

Tailwind CSS is a utility-first CSS framework.

Instead of the opinionated classes you're used to from frameworks like Bootstrap, which combine multiple different CSS properties -- e.g., .card sets position, width, display, border, and background color -- Tailwind provides primitive utility classes where one class does one thing -- e.g., .bg-white sets the background color to white.


  1. It's highly customizable
  2. It uses a mobile-first approach
  3. It's very simple to style a dark version of your design
  4. The change (generally) applies to a single element, meaning you won't break something somewhere else
  5. Once you understand how Tailwind works, it's very easy to add classes out of your head without Googling them
  6. There are official extensions for VSCode and JetBrains IDEs


  1. CSS is tightly coupled with HTML
  2. Since each class takes care of only one thing, a single element can use umpteen classes, thus hurting readability
  3. It doesn't provide components out-of-the-box, although there are free and payable solutions available -- e.g., Tailwind Elements or Flowbite
  4. Lack of components and abundance of classes makes for a relatively steep learning curve

An example of what Tailwind looks like:

<div class="my-4 mx-auto text-sm lg:text-2xl text-slate-700">
  Tailwind CSS is a utility-first CSS framework.

In this example, we determine the size of margin (my for top and bottom, mx for left and right), font-size, and line-height (text-sm, text-2xl) as well as the color and shade of the text (text-slate-700, text-slate-300).


Alpine.js is a lightweight JavaScript framework with, as of writing, only 15 attributes, 6 properties, and 2 methods. Its creator calls it the "jQuery for the modern web".


  1. It's lightweight
  2. Syntax is similar to Vue and Angular, so Alpine.js will feel familiar if you have used any of them before
  3. You don't necessarily need to adapt your code to the framework -- you can just sprinkle Alpine.js where needed
  4. It's written in an HTML file, making it ideal to combine with HTMX and Tailwind


  1. Unlike HTMX, which doesn't look much like JavaScript, Alpine.js still feels like one of the modern JavaScript frameworks -- e.g., it uses components. Some solutions also require writing plain JavaScript.
  2. Using Alpine.js in all its powers requires the data to be in an Alpine component. This means that you need to transfer your data from Django to Alpine (we won't be covering this approach in this course).
  3. You have to (mostly) pay for Alpine.js components (e.g., an accordion).

An example of what Alpine.js looks like:

<div x-data="{ count: 0 }">
  <span x-text="count"></span>
  <button x-on:click="count++">Increment</button>

This code creates a very simple counter:

  1. x-data creates the component and provides the data for it (count).
  2. The span and the button are both connected to the count data.
  3. x-text="count" sets the text content of the element (to count) and x-on:click="count++" increments the count value each time the button is clicked.

Django with Tailwind CSS, HTMX, and Alpine.js

Django is a powerful tool with a great templating language. That said, since it's a backend framework, it lacks dynamic functionality.

Often, the solution is to use Django Rest Framework combined with a fully-fledged framework, such as Vue or React. But, as mentioned, this can add unnecessary complexity to a project when you just want to sprinkle a bit of dynamic functionality on top.

That's where HTMX and Alpine.js come in. The complexity they add is relatively small, but the functionality and smoothness they add can mean a significant change in the user experience of your app. While Alpine focuses on client-side state and operations, HTMX focuses on interaction with your server.

Although part of the HTMX documentation is examples of usage, integrating it with Django requires a good understanding of both and can be challenging at the beginning. Another challenge you'll face when using Django with HTMX is the organization of your code. HTMX requires a lot more views and, consequently, URLs and templates -- you can't just dump them in one file and expect to be able to find or understand anything.

The app also needs to look good of course. In the spirit of not dragging half the internet into your app and its rising popularity, Tailwind is an obvious choice. Using it with Django templates is effortless, although it is a little complicated to include it at the start.

What Does This Course Cover?

This course has three parts.

Part 1

In Part 1, you'll learn the basics about each tech stack that we'll be using:

  1. Setting up the Django project
  2. What is HTMX, and how to integrate it with Django
  3. What is Tailwind, and how to integrate it with Django
  4. What is Alpine.js, and how to integrate it with Django

Part 2

In Part 2, we'll add HTMX, Tailwind CSS, and Alpine.js to a Django project.

  1. We'll go through the common use cases of using HTMX with Django:
    1. Click-to-edit-pattern
    2. Inline validation
    3. Editing a row in a table
    4. Removing a row from a table
    5. Adding a row to a table
    6. Bulk update
    7. Search and filtering
    8. Infinite scroll
  2. In the templates, we'll use Tailwind's classes
  3. Finally, we'll add Alpine.js into the mix to improve the use case where HTMX can't:
    1. Tooltip
    2. "Select all" checkbox
    3. Showing additional data upon demand

Part 3

In Part 3, we'll productionize our app using the following tools:

  1. AllAuth (with Google and Facebook social auth)
  2. Cypress (to test some of the functionality we added with HTMX and Alpine.js)
  3. WhiteNoise
  4. Gunicorn
  5. Postgres
  6. Docker and Docker Compose

Finally, we'll deploy the application to Heroku.

What You'll Be Building

HTMX combined with Django is great for apps that manage data -- adding, changing, and removing some data. We'll create a party organizer app that covers the following use cases:

  1. Creating a new party
  2. Listing the parties
  3. Editing a single party
  4. Listing gifts in a table format
  5. Adding a new gift as a record on a table
  6. Editing and removing a gift row in a table
  7. Listing party guests with their attending status
  8. Updating the attending status for multiple guests
  9. Searching and filtering through the guest list
  10. Infinite scroll through the parties list

Mark as Completed