Lightweight Javascript Framework Review (For Django Developers)

Table of Contents


Many developers are confused when they try to find a Lightweight Javascript Framework for the Django project because there are so many options.

In this post, I will talk and compare lightweight javascript frameworks, and help you decide which one is the best for your Django project.

If you want to:

  1. Render HTML in Django, and use Javascript to enhance the server-rendred HTML.
  2. Say no to the heavy frontend solutions such as React, Vue, Svelte, Angular
  3. Say no to the Decoupled Architecture

Then this post can help you!

Why this Post is Unique

Unlike other posts which only compare existing solutions in the Django community, I will also talk about solutions in other communities (Phoenix, Rails, Laravel) and how they influenced each other.

Below is a list of frameworks that I will talk about in this post

  1. Phoenix LiveView
  2. Laravel Livewire
  3. Hotwire (Turbo, Stimulus)
  4. StimulusReflex
  5. Catalyst
  6. HTMX
  7. Unpoly
  8. Alpine.js
  9. Django Reactor
  10. Django Unicorn
  11. django-sockpuppet
  12. Tetra

I hope that can help you get a better understanding.

Phoenix LiveView

At first, I'd like to talk about Phoenix LiveView, because it is so important and inspired so many javascript frameworks.

Phoenix is a web development framework written in the functional programming language Elixir.

Phoenix LiveView is library which enables rich, real-time user experiences with server-rendered HTML.

The LiveView applications are stateful due to the bidirectional communication with WebSockets.

Events in LiveView are regular messages which may cause changes to its state. Once the state changes, LiveView will re-render the relevant parts of its HTML template and push it back to the browser, which updates itself in the most efficient manner.

A LiveView begins as a regular HTTP request and HTML response, and then upgrades to a stateful view on client connect (Websocket). Any time a stateful view changes or updates its socket assigns, it is automatically re-rendered and the updates are pushed to the client.

For example, to react to a click on a button:

<button phx-click="inc_temperature">+</button>

On the server

def handle_event("inc_temperature", _value, socket) do
  {:ok, new_temp} = Thermostat.inc_temperature(
  {:noreply, assign(socket, :temperature, new_temp)}

After receiving the message, the server re-rendered the HTML, push it back to the client via Websocket, the page UI can be updated and user feel it is real-time.

Phoenix LiveView

With LiveView, in many cases, developers do not need to write a single line of JavaScript to build feature.

But you should also know, in some cases, developers still need to sprinkle JavaScript behavior directly into the HTML. For example, tabs, dropdowns, popovers and modals.


As we already know, WebSocket is very important in Phoenix LiveView, since it makes bidirectional communication between the client and the server possible.

Elixir is a functional, concurrent, general-purpose programming language that runs on the BEAM virtual machine which is also used to implement the Erlang programming language. Elixir builds on top of Erlang and shares the same abstractions for building distributed, fault-tolerant applications

You should also know, other programming languages such as PHP, Ruby, Python can not do the same as Elixir in the Concurrency area.

And you can check this post Phoenix Channels vs Rails Action Cable

So developers in other community usually solve the problem in this way:

  1. User trigger some event (click the button)
  2. Use HTTP to transfer event from the client to the server. (not Websocket)
  3. Server re-render the component HTML, and return HTML back in HTTP response.
  4. Extract HTML from the HTTP response and update page UI.

Next, let's take a look at some other javascript frameworks.

Django Community

  • Django Reactor is a port of Phoenix LiveView, it depends on Django channels, a package which provides Websocket support.
  • Django Unicorn is inspired by Phoenix LiveView, but it does not depend on Websocket. When event fired on the browser, it sends AJAX request to the backend, and then use the response to update the page UI.

For example, to react to a click on a button in Django Unicorn

<button unicorn:click="increment">+</button>

The server component

from django_unicorn.components import UnicornView

class ClicksView(UnicornView):
    count = 0

    def increment(self):
        self.count += 1

From Github stats, more people like Django Unicorn than Reactor, I guess that is because:

  1. Django Unicorn does not depend on Websocket, which makes the infrastructure simpler.
  2. The attributes (for example unicorn:click) is cleaner than using Django templatetag.

Laravel Community

Laravel is the most popular web framework in the PHP community.

Laravel Livewire is a full-stack framework for Laravel, which is also inspired by Phoenix LiveView

Let's take a look at the counter example.

<button wire:click="increment">+</button>

The server side:

class Counter extends Component
    public $count = 0;

    public function increment()

    public function render()
        return view('livewire.counter');

Livewire also use HTTP to transfer data, but it can also work with websocket to provide real-time functionality.

Actually Livewire and Alpine.js are created by ONE developer, Caleb Porzio, they can work together very well and people in Laravel community usually choose this combination.

Below is a component, which use both Livewire and Alpine.js:

<div x-data="{ open: false }">
    <button @click="open = true">Show More...</button>

    <ul x-show="open" @click.outside="open = false">
        <li><button wire:click="archive">Archive</button></li>
        <li><button wire:click="delete">Delete</button></li>
  1. The Alpine.js can do the pure frontend work.
  2. The Livewire is used to communicate with the backend server.

This project also inspired Django developers and there is a project, you can check if if you are interested.

Now we have seen some solutions which are inspired by Phoenix LiveView, next, let's take a look at another way to solve the Javascript issue.


Stimulus is part of the Rails official frontend solution Hotwire.

Stimulus is a JavaScript framework with modest ambitions, is designed to enhance static or server-rendered HTML, by connecting JavaScript objects to elements on the page using simple annotations.

These JavaScript objects are called controllers, and Stimulus continuously monitors the page waiting for HTML data-controller attributes to appear. For each attribute, Stimulus looks at the attribute’s value to find a corresponding controller class, creates a new instance of that class, and connects it to the element.

For example, we have HTML:

<div data-controller="hello">
  <input data-hello-target="name" type="text">
  <button data-action="click->hello#greet">Greet</button>

And we have hello_controller.js

import { Controller } from "@hotwired/stimulus"

export default class extends Controller {
  static targets = [ "name" ]

  greet() {
    const element = this.nameTarget
    const name = element.value
    console.log(`Hello, ${name}!`)


  1. When Stimulus detect data-controller="hello", it will create a new controller instance from hello_controller.js and attach it to the HTML.
  2. data-action="click->hello#greet" means, if user click the button, great method in hello controller will be executed to handle the JS event.

Stimulus is a pure frontend framework, and can be used with ANY backend framework.

  1. I highly recommend you to read Stimulus 1.0: A modest JavaScript framework for the HTML you already have, published in 2018, by DHH, the creator of Ruby on Rails.
  2. You can also check Stimulus official doc to learn more.


StimulusReflex extends capabilities of both Rails and Stimulus by intercepting user interactions and passing them to Rails over real-time websockets.

StimulusReflex can make Rails app work like Phoenix LiveView (it is also inspired by LiveView), by using the Rails ActionCable, which integrates WebSockets with Rails.

For example:

<a href="#"
  data-count="<%= @count.to_i %>"
>Increment <%= @count.to_i %></a>

The data-reflex attribute allows us to map an action on the client to code that will be executed on the server.

class CounterReflex < ApplicationReflex
  def increment
    @count = element.dataset[:count].to_i + element.dataset[:step].to_i

On the server, we use element.dataset[:count] to access the data-count, increment the value by 1 and set @count. The Reflex will then push the DOM back to client via Websocket. At last, client will update the page UI.

Please check to know more about StimulusReflex

This project also inspired Django developers and there is a project django-sockpuppet, which is port of the StimulusReflex.


Turbo derived from Rails Turbolinks package, now it is also part of Rails official frontend solution (Hotwire).

Stimulus pairs beautifully with Turbo to provide a complete solution for fast, compelling applications with a minimal amount of effort.

One interesting feature brought by Turbo is Turbo Stream

A Turbo Streams message is a fragment of HTML consisting of <turbo-stream> elements. The stream message below demonstrates the seven possible stream actions

<turbo-stream action="append" target="messages">
    <div id="message_1">
      This div will be appended to the element with the DOM ID "messages".

With Turbo Stream, we can return HTML from the server to manipunate page DOM without using Javascript, this can let us build many powerful features such as chat room, auto complete search box, and etc.

Another feature of Turbo is Turbo Frame

Turbo Frames allow predefined parts of a page to be updated on request. Any links and forms inside a frame are captured, and the frame contents automatically updated after receiving a response

Turbo Frame provide an opportunity to decompose pages into self-contained fragments.

Both Turbo Stream and Turbo Frame are inspired by Phoenix LiveView

I highly recommend you to check this youtube video What is Hotwire?


Web Components is a suite of different technologies allowing you to create reusable custom elements

Stimulus inspired Catalyst Catalyst, GitHub’s web component set of patterns.

Github is the web component lover, Stimulus inspired them to build Catalyst, which help developers to build web components using Stimulus like syntax.

The backend Component can have one-to-one relationship with frontend web component, allowing developers to work on a single abstraction for both front-end and backend.

  <input data-target="" type="text">

  <button data-action="click:hello-world#greet">

  <span data-target="hello-world.output">
import { controller, target } from "@github/catalyst"

class HelloWorldElement extends HTMLElement {
  @target name: HTMLElement
  @target output: HTMLElement

  greet() {
    this.output.textContent = `Hello, ${}!`

Catalyst is a very interesting tech, and you can check links below to learn more.


HTMX allows you to access AJAX, CSS Transitions, WebSockets and Server Sent Events directly in HTML, using attributes

<button hx-post="/clicked" hx-swap="outerHTML">
  Click Me
  1. The attributes have hx prefix
  2. The above HTML code means: when a user clicks on this button, issue an AJAX request to /clicked, and replace the entire button with the response

With HTMX, the workflow is very simple, client trigger event and send request, the server return HTML, and then the client update the page according to the response.

HTMX focus more on the communication between the server using Ajax, it seems like Django Unicorn, but the backend does not need to implement stateful component, just render HTML with Django view and get things done.

You can check to know more about HTMX.

There is a project Unpoly which do similar work as HTMX, you can check if you are interested.


Alpine is a rugged, minimal tool for composing behavior directly in your markup.

Let's check an example counter built by Alpine.js

<div x-data="{ count: 0 }">
    <button x-on:click="count++">Increment</button>
    <span x-text="count"></span>
  1. With x-data, we declared a counter component, which has count=0
  2. x-on:click means if user click the button, the count++ will run.
  3. x-text="count" means the DOM element will display the count value of the component.

As you can see, it is easy to use Alpine.js to sprinkle JavaScript behavior directly into the HTML.

Alpine.js directives syntax is very similar with Vue.js, because Vue.js is very popular in the PHP community.

Alpine.js is a good option to deal with pure frontend stuff, for example: tabs, modals.

Please check if you want to know more about Alpine.js

Rough Comparison

Now, we already know many lightweight javascript frameworks, next, we will compare some of them and find out some promising solutions.

Django Reactor

As we know, this package needs you to install Django channels, a package which provides Websocket support.

Compared with HTTP, Websocket needs more resources on the server (for example: redis connections), and make things more complicated when you need to scale your web application.

That is why I do not recommend any frontend framework which require Websocket (Django Reactor, django-sockpuppet)

Actually, if you take a look at Laravel Livewire, and Hotwire (official frontend framework in Rails community), they use HTTP to transfer data by default, and Websocket is optional.

Django Unicorn

Django Unicorn use HTTP to transfer data, and the attributes are clean.

It can help the client do communication between the server using Ajax without writing Javascript.

If you want to sprinkle JavaScript behavior directly into the HTML (tabs, dropdowns, popovers and modals), you might still need vanilla javascript or other framework such as Alpine.js

django-unicorn + Alpine.js can be a promising solution.


HTMX also focuses more on the communication between the server using Ajax. Unlike Django Unicorn, it is a generalized framework, and developers can user it with any backend framework.

When using HTMX with Django, developers can still use Django views to return HTML, no need to touch component, which is straightforward.

If you want to sprinkle JavaScript behavior directly into the HTML (tabs, dropdowns, popovers and modals), you have below options:

  1. Vanilla javascript
  2. Alpine.js
  3. hyperscript is a sister project of HTMX

From the community, many people like HTMX + Alpine.js solution, for example:

HTMX + Alpine.js can be a promising solution.

Hotwire (Turbo + Stimulus)

As we know, Hotwire is the official frontend solution in the Rails community, which includes Turbo and Stimulus.

The Turbo package contains:

  1. Turbo Drive accelerates links and form submissions by negating the need for full page reloads. Which bring SPA experience to our app.
  2. Turbo Frames decompose pages into independent contexts, which scope navigation and can be lazily loaded.
  3. Turbo Streams deliver page changes over WebSocket, SSE or in response to form submissions using just HTML and a set of CRUD-like actions.
  4. Turbo Native help build hybrid apps for iOS and Android

The Stimulus is designed to enhance static or server-rendered HTML, by connecting JavaScript objects to elements on the page using simple annotations

  1. We can see Turbo as a competitor of HTMX, they communicate with the backend server.
  2. Stimulus can be seen as a competitor of Alpine.js, they do pure frontend work.

Hotwire can be used with ANY backend framework, and it is also very popular in the PHP community (Symfony).

So Turbo + Stimulus can be a promising solution.

Which Solution Should I Choose

Now we got below promising solutions:

  1. django-unicorn + Alpine.js
  2. HTMX + Alpine.js
  3. Turbo + Stimulus (Hotwire)

In the Django community, it seems HTMX + Alpine.js is more popular than django-unicorn + Alpine.js, since they are similar, I will only talk about HTMX + Alpine.js in the next sections. If you prefer django-unicorn, please feel free to switch.

Next, I will compare HTMX + Alpine.js with Turbo + Stimulus.


Let's first take a look at Hotwire.

Hotwire is the official frontend framework in Rails, it is build by people at Basecamp, and powers the Basecamp, HEY email service. (and other Rails web applications)

Both Basecamp and HEY are large web applications, which means Hotwire is solid and works good as your codebase grow bigger and bigger.

And there are many high quality tutorials, open source projects about Hotwire. Since Hotwire is pure frontend framework, we can easily make them work with any backend web frameworks. (Symfony, Django, Flask...)

  1. Awesome StimulusJS
  2. Turbo Showcase

Backed by Rails community and some successful business companies, Hotwire has healthy ecosystem.

In most cases, if you meet problem, you can easily find answer at StackOverflow, someone's blog, or Github.

Next, let's take a look at Alpine.js and HTMX

Alpine.js is very popular, and there are many resources about it.

From what I see, most of the blogs, videos, open sources projects about HTMX, come from Python developers (Django, Flask), I guess that is because Laravel and Rails already have default frontend solution, so most developers would not try HTMX.

I tried to find what business companies that are using Alpine.js + HTMX to build what products, but I can not find that info.

So the ecosystem of Hotwire seems better than Alpine.js + HTMX, and the large web applications give developers confidence that they can stick with it as the project grow.

Difficulty of Installation

HTMX + Alpine.js is easy to install, we can import via CDN link, no need transpile or bundle solution.

<script src="[email protected]"></script>
<script src="[email protected]/dist/cdn.min.js"></script>

And then you can write code to enhance the HTML, which is cool!

To install Turbo + Stimulus in your Django project, now you need a bundle solution.

Some Django developers complain they do not like to touch tedious Webpack config or frontend config stuff. Fortunately, the project python-webpack-boilerplate can help you create frontend project which bundled by Webpack within minutes, even you have no idea how to config Webpack.

python-webpack-boilerplate also has detailed doc and pre-defined config to help you keep Javascript and CSS/SCSS in good style.

From Rails 7, it introduced a new tech importmap to use JavaScript libraries made for ES modules (ESM) without the need for transpiling or bundling.

In the future, Django developers can switch to importmap and drop the Webpack, and some people already did attempt. django-importmap

So HTMX + Alpine.js is easy to install than Turbo + Stimulus, but many people overestimate the difficulty.

Because I am pretty sure a beginner Django developer can use python-webpack-boilerplate to install Turbo + Stimulus within ONE hour.

Directives or Vanilla Javascript

HTMX has about 30 custom attributes (also called directives).

Alpine.js has about 18 custom attributes (also called directives).

For some people, directives is the killer feature, which can let them get things done directly in the template.

However, the directives adds a new layer of complexity, even people who already learned Javascript, still need to learn the syntax (for example, what does hx-trigger="keyup changed delay:1s" mean?).

The Stimulus in Hotwire only use attributes to pass value from the server-rendered HTML to the Stimulus Controller, and the Stimulus Controller is written with normal Javascript syntax, which is more friendly to Javascript people.


About 10 years ago, when we build web application, we are told we better decouple HTML, CSS, and JavaScript

At that time, we write Javascript code in a separate JS file with the help of framework such as jQuery.

  1. If we want to change content, we edit HTML
  2. If we want to change style, we edit CSS
  3. If we want to change frontend behavior, we change Javascript.

Let's take a look at the Alpine.js official Dropdown component

<div class="flex justify-center">
            open: false,
            toggle() {
                if ( {
                    return this.close()


       = true
            close(focusAfter) {
                if (! return

       = false

                focusAfter && focusAfter.focus()
        x-on:focusin.window="! $refs.panel.contains($ && close()"
        <!-- Button -->
            class="bg-white px-5 py-2.5 rounded-md shadow"
            <span aria-hidden="true">&darr;</span>



  1. This is part of the full code.
  2. Do you really believe writing Javascript in your template file is a good idea?
  3. If you check the code after 1 month, could you quickly understand what the Alpine.js component do?
  4. If you need to update some logic, you might need to change multiple templates?

Even Alpine.js provides a way to register component (, so developers can reuse the code. From what I see, very few Django developers do that.

Stimulus encourage developers to write Javascript in .js, and one controller is for one one type of work. (For example: dropdown_controller.js), this helps the frontend code better organized.

After you write some Stimulus components, you can reuse them in convenient way.

You can check Stimulus Components, which can help you understand and follow the DRY (Don't Repeat Yourself) principle with Stimulus.

Code Quality, Code Linting

TypeScript is a strict syntactical superset of JavaScript and adds optional static typing to the language.

Both Stimulus and Turbo are written with TypeScript (Update: Turbo 8 has dropped Typescript and use regular Javascript

And you can also write Stimulus controller with TypeScript is an interesting project, it learned from React Hook and bring the use syntax to Stimulus. So we can just write callback method in the Stimulus controller.

As a 3-party package which will be used by other people, it is devel in TypeScript, for better code quality.

Since the Stimulus Controller is a Javascript file, developers can also use mature code linting tool such as Eslint and Prettier to keep the Javascript code in good style.

Let's take a look at Alpine.js + HTMX

  1. Both Alpine.js and HTMX are written with regular Javascript.
  2. Some people also shared TypeScript solution in Alpine.js, but very few people use it.
  3. The code linting tool can not help you detect Javascript very well in the template.

Learning Curve

Alpine.js + HTMX is easy to install and get started, but things might become a little complicated as your project grow.

Turbo + Stimulus is a little hard to install and get started at first, but once you understand the concepts, the rest is to build feature with Vanilla javascript. You do not need to remember custom directives such as (hx-trigger, hx-get, hx-post), or the argument syntax such as keyup changed delay:1s.


If you want to start quickly (prototyping) and do not like to learn Javascript, you can choose Alpine.js + HTMX (or Alpine.js + django-unicorn).

If you do not mind learning some Javascript, and want a solid solution, please try Hotwire. You might feel slow at first, but after you learn it, you will like it and you do not need to switch to another solution until your product exceed the size of, or


Are there good learning resources about Hotwire?

When this post is written, it seems HTMX + Alpine.js is much more popular in Django community and there are more resources about it.

If you want to know more about Hotwire + Django/Flask, you can check this

Should I use Turbo and Stimulus together?

They are built to solve different problems, and you can use one of them in your project.

For example, you can use Stimulus as a replacement of jQuery.

If possible, you better use them together for better user experience.

Launch Products Faster with Django

Unlock the power of Django combined with Ruby on Rails frontend through SaaS Hammer. Supercharge productivity, tap into Python's rich ecosystem, and focus on perfecting your product!

Michael Yin

Michael Yin

Michael is a Full Stack Developer from China who loves writing code, tutorials about Django, and modern frontend tech.

He has published some ebooks on leanpub and tech course on

© 2024 SaaS Hammer