Combining Rails Hotwire with Django to Ship your SaaS Faster

Table of Contents

Introduction

My name is Michael Yin, and I am a full stack developer who primarily uses Django and modern frontend technologies to build web applications. I am also a tech writer who has published courses and ebooks on Wagtail CMS and Celery on platforms such as testdriven.io and leanpub.com.

In recent years, Alpine.js + HTMX has gained popularity within the Django community as a lightweight frontend solution. However, Hotwire, another modern frontend technology stack loved by developers in other communities, has not received much attention in the Django community.

As a Django developer who has been utilizing Hotwire for the past few years, I have found it to be a powerful stack. This experience has motivated me to write a blog post to inspire and assist other Django developers seeking a reliable solution for their SaaS businesses.

By the end of this post, you will have a better understanding of what Hotwire is, how it functions, and why integrating it with Django can help you expedite the development of your SaaS.

Ruby on Rails

Let's talk about Ruby on Rails first.

Similar to Django, Ruby on Rails is a full-stack web application framework that provides all the necessary components to create full stack web applications following the Model-View-Controller (MVC) pattern.

In contrast to Django, Ruby on Rails comes with built-in frontend solutions that enhance productivity, some of which have had a significant impact on the web development community.

Many years ago, writing CSS code was a challenge for developers, leading to the creation of solutions such as LESS, SASS, and SCSS to improve the maintainability of CSS.

  1. Less: The initial version of Less was written in Ruby and was used in Ruby on Rails.
  2. Sass/Scss: Developed by Hampton Catlin, a Ruby developer, Sass/Scss was first utilized in Ruby on Rails.

The above styling solutions were initially crafted by Ruby developers and later gained popularity across the entire web development community.

What about JavaScript:

  1. CoffeeScript is a programming language that compiles to JavaScript. It adds syntactic sugar inspired by Ruby, Python, and Haskell in an effort to enhance JavaScript's brevity and readability. CoffeeScript was officially integrated into the Ruby on Rails framework starting with Rails version 3.1. Another JavaScript transpiler, Babel, is inspired by CoffeeScript, and built after 5 years of CoffeeScript's release.
  2. Turbolinks is a JavaScript library that speeds up web navigation by updating specific parts of a page without fully refreshing it, providing a faster user experience similar to a single-page application.
  3. Rails Webpacker package integrates the Webpack module bundler with Ruby on Rails, simplifying the management of frontend assets and enabling the use of modern front-end technologies in Rails applications.

The Rails framework and its community have good taste, and they have created some robust frontend solutions, which have supported many startups grow from zero to hero.

Evolution Rather Than Revolution

Over the past few years, the landscape of web development has undergone a significant transformation with the emergence of frontend technologies like React, Vue, and Angular. They have revolutionized the way web applications are built.

  • Backend as an API: The backend now primarily serves as an API, with data being exposed through REST API or GraphQL.
  • Decoupled Architecture: Web applications now follow a decoupled architecture, separating the frontend and backend.

While these changes represent a revolution in web development, they also come with their challenges.

  1. Developers must invest more time in learning and troubleshooting issues.
  2. The separation of frontend and backend adds complexity to the development process.

People might ask:

  1. Is it possible to go back to the monolithic architecture?
  2. Can we render HTML from the server and tightly integrating the frontend with the backend?
  3. Can we just build logic on the backend without having to do the same thing on the frontend?
  4. Can we still bring SPA like experience to our users?

Let's explore Hotwire, the answer from the Rails community to the above questions.

Hotwire Basics

Hotwire is the official frontend solution shipped with Ruby on Rails.

Hotwire (HTML over the wire) is an alternative approach to building modern web applications without using much JavaScript by sending HTML instead of JSON over the wire.

Hotwire actually contains multiple techs, and they follow the Unix philosophy: "Do one thing and do it well". So let me introduce them one by one:

Please take a look at this video first to do quick check of the Hotwire techs.


Turbo

Turbo is essentially an evolution of the Turbolinks, building on top of its core functionality and adding new features to further enhance web performance and interactivity

  1. Turbo Drive accelerates links and form submissions by negating the need for full page reloads, which bring the SPA-like experience to the traditional server-rendered apps. You can see it as successor of Turbolinks.
  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 HTTP response to form submissions using just HTML and a set of CRUD-like actions.
  4. Turbo Native allows developers build hybrid iOS and Android apps from the same codebase, using the same HTML, CSS, and JavaScript that powers the web apps.

You can check Turbo Doc to learn more.

As for deep dive of Turbo Frame and Turbo Stream, please check this video.


Stimulus

Stimulus is a modest JavaScript framework that pairs beautifully with Turbo. It provides just enough structure to enhance your HTML with interactivity, without getting in the way of your work. You can see it as successor of jQuery.

Here is Stimulus Doc

You can check this video for a quick overview. (It is recorded when Stimulus 1.0 is released, but the basic concept is still the same)


Strada

Tubo Native and Strada are both solutions for native mobile apps, which can help you convert the web app built with Turbo into a native mobile app.

This is a very interesting topic, and I wish you can check it out.

If you want to know how Turbo Native works, please check


Strada enables you to create fully native controls in your hybrid mobile apps, driven by the web. Build web components and native components that work together in WebView screens to elevate your Turbo Native apps to the next level.

Check Strada Doc if you are interested or check this video


Should I Use Them All?

While Hotwire offers a variety of technologies, it is important to note that you are not required to use them all.

  1. Turbo is the core of Hotwire, you can use it alone to speed up your web app. For example, you can use Turbo with other tech such as Web Component, SolidJS, etc.
  2. You can only use Stimulus to add interactivity to your web app as a replacement of jQuery.

In most cases, Turbo + Stimulus is recommended and enough for most web apps, but you can make your own choice based on your project requirements.

Backend Agnostic

Some individuals may wonder if Hotwire, originating from the Rails community, can be utilized with other backend languages such as PHP, Python, Java, and more?

The answer is YES.

Hotwire is backend agnostic, making it compatible with ANY backend language. As long as your server can render HTML, you can incorporate Hotwire into your project.

While Ruby on Rails includes helper methods to facilitate the use of Hotwire, there is nothing inherently tied to Rails within Hotwire itself.

  1. Hotwire has been officially adopted as a frontend solution for Symfony, a PHP web framework. Resources such as Symfony UX: Turbo and Symfony UX showcase this integration. Additionally,
  2. Miguel Grinberg has developed a helper library for Flask called turbo-flask.

It is obvious that Hotwire is not limited to Ruby on Rails. It can be seamlessly integrated with a wide range of backend web frameworks.

Ecosystem

Hotwire has very rich ecosystem, you can find many useful tools and resources to help when you need.

Hotwire

hotwire.io: Community-maintained Hotwire Hub, and you can find many useful resources about Hotwire here.

Stimulus

  1. better-stimulus, An opinionated collection of StimulusJS best practices
  2. tailwindcss-stimulus-components A set of StimulusJS components for TailwindCSS apps
  3. Stimulus components, A set of StimulusJS controllers to solve common patterns.
  4. stimulus-use, Stimulus-use is a library that allows you to use hooks in Stimulus controllers, which is similar to how React hooks work in React components. Both provide a way to manage state and side effects in a functional component-based architecture.

By exploring the above links, you can see that Stimulus is a great way to reuse code, whether it is built by you or by other people.

Other Resources

Below sites have high quality content on Hotwire:

  1. Evil Martians
  2. Thoughtbot blog
  3. 37signals blog

As the official frontend solution shipped with Rails, there are many 3-party libraries, tutorials, blog posts, videos available to assist you in completing tasks more efficiently.

Scalability

Another question people might ask is "Can Hotwire be used in large scale web apps?"

  1. Hotwire, as the official frontend solution of Rails, has been successfully used in many large scale web apps, including GitHub, Basecamp, Hey, and more.
  2. There are numerous blog posts and videos discussing the scaling and implementation of various UI features using Hotwire.
  3. You can also make Hotwire coexists with other frontend solutions, such as React, Vue, and only use them to render the complex components. The art of Turbo Mount: Hotwire meets modern JS frameworks

GitHub Is Using Hotwire

Here I want to talk a bit more about GitHub.com, as of 2023, there are already more than 100 million developers using GitHub, and it was estimated to generate $1 billion in revenue.

Hotwire is currently used on GitHub.com, if you visit GitHub and check the source code in browser, you might see something like this:

  1. The data-turbo="false" means the form will be submitted in the traditional way instead of using Turbo Drive, It appears that GitHub is utilizing Turbo Drive for page navigation, which is why clicking on links for page navigation on GitHub.com results in a smooth transition.
  2. data-action is a syntax from Stimulus

Since GitHub.com is a large-scale application, it likely addresses more complex UI issues. While they did not use Stimulus directly, they were inspired and released Catalyst as a solution for their UI needs.

Catalyst is a set of patterns and techniques for developing components within a complex application. At its core, Catalyst simply provides a small library of functions to make developing Web Components easier.

The Web Systems team at GitHub explored other tools that adopt these set of patterns and principles. The closest match to those goals was Stimulus (from which Catalyst is heavily inspired)

I also recommend you to check How we use Web Components at GitHub

Benefits of Combing Hotwire with Django

Benefits of Python:

  1. Python is a versatile and easy-to-learn programming language, making it a popular choice for beginners and experienced developers.
  2. Python has a thriving ecosystem, with the rise of artificial intelligence (AI) and machine learning (ML), Python is becoming more and more popular.
  3. There are numerous third-party packages available on the Python Package Index (PyPI) that can greatly streamline your development process and save you valuable time.

Django is a web framework built on Python, and it also has many benefits:

  1. Django is a high-level web framework that simplifies the process of building web applications by providing a set of tools and libraries for common tasks.
  2. Django has extensive documentation, and the ecosystem is very rich, with many third-party packages and resources available, making it easy to find support and additional resources when building applications.

If we combine Hotwire and Django, then we can get a powerful solution which offers numerous benefits:

Frontend:

  1. Hotwire is built by people with expertise in frontend technologies, and they do care about productivity.
  2. Hotwire has been adopted by numerous successful startups, with its solid code quality and scalability capabilities being well-demonstrated.
  3. Hotwire boasts a thriving ecosystem with a wealth of third-party resources available. This makes it easy to find solutions and support if you encounter any challenges.
  4. Hotwire is still evolving and will continue to do so. In Turbo 8, with new Morphing feature, elements on a web page can smoothly transition from one state to another, and InstantClick can decrease the page load time, which seems exciting!

Backend:

  1. Python/Django can still be utilized to develop the backend of the web application, especially among startups that favor Python over Ruby.
  2. We can take advantage of Django's built-in auth, form, templates, extensive documentation, and numerous third-party Django packages like django-allauth with minimal effort.
  3. As for existing Django project, we can still integrate Hotwire and bring modern user experience, without refactoring the whole project.

Hotwire + Django can bring productivity and help developers ship SaaS faster.

Learning Curve of Hotwire

There is a learning curve initially for developers using Hotwire.

Developers need to understand the basic concepts of Hotwire, such as Turbo Drive, Turbo Frame, Turbo Stream and Stimulus.

Once you have a solid understanding of the basic concepts, you will find Hotwire very user-friendly and beneficial for increasing productivity.

In my personal experience, learning Hotwire feels similar to learning VIM - a bit challenging at first, but as you become accustomed to it, everything becomes more straightforward.

Good Resources to Learn Hotwire with Django

Many online resources about Hotwire + Django seems little outdated, if you want to know more, please check below links:

  1. awesome-hotwire-python
  2. https://hotwire.io/frameworks/django

How to Install Hotwire with Django

From this section, I will start talking about integrating Hotwire with Django

When it comes to installation, even Hotwire doc say you can install it using CDN link, I still recommend you to install frontend packages via NPM with frontend bundle solution, which can help you manage the frontend dependencies better, and do code linting and formatting on the frontend code.

Below packages can help:

  1. python-webpack-boilerplate (developed by me)
  2. django-vite

python-webpack-boilerplate is to help bring modern frontend tooling to Django project, it also contains pre-configured code linting tools (like ruff for Python), which can help make the codebase more consistent.

After you make the frontend tooling ready, you can then install the NPM packages and start using Hotwire.

Some Django developers do not want to touch npm command, but if modern frontend tooling can help you write reusable, cleaner code with fewer bugs, it is a good investment.

For a comprehensive, step-by-step guide, please refer to the Hotwire Django Tutorial, it is a free ebook which help Django developers to learn Hotwire.

Buildless

Ruby on Rails also provides a way to use Hotwire without build tools, it is called importmap, you can check JavaScript without npm? A dive into Rails 7's importmap approach and https://github.com/rails/importmap-rails for more details

You can give it a try if you are interested, but please note it has some limitations, and I do not recommend this approach for now.

Common Issues for New Hotwire Users

Form Submission Issue with Django

By default, Turbo intercepts all clicks on links and form submissions.

When a form submission fails validation on the server side, Turbo expects the server to return a 422 Unprocessable Entity status code.

However, in Django, failed form submissions still return an HTTP 200 status code, causing issues when working with Turbo Drive.

To solve this issue, you can use the turbo_helper.middleware.TurboMiddleware from django-turbo-helper. This middleware can detect POST requests from Turbo and automatically change the response status code to 422 if the form validation fails.

This should work seamlessly with Turbo 8+ on the frontend, eliminating the need for developers to manually set the status code to 422 in the view.

For more information, refer to the https://turbo.hotwired.dev/handbook/drive#redirecting-after-a-form-submission

DOMContentLoaded

You may have Javascript fired on DOMContentLoaded event (for example: Google Analytics), to make it work with Turbo, you need to use turbo:load event, which is similar to DOMContentLoaded event.

document.addEventListener("turbo:load", function() {
  // ...
})

Strategy to Integrate Hotwire with Django

I want to share some strategies for integrating Hotwire with Django.

Strategy 1

If you are new to Turbo and want to use Turbo with existing Django project, you can use this approach.

  1. Add data-turbo="false" to body element or use Turbo.session.drive = false to disable Turbo Drive globally.
  2. And use data-turbo="true" to enable it on specific elements (for example: form) and make it work on specific pages.

This is the simplest way that will not break your existing project.

Strategy 2

If you're building a SaaS application from scratch using Django, Turbo Drive is enabled by default, just enjoy it!

Use Django-Turbo-Helper to Simplify Turbo Usage

As mentioned, while Rails offers helper methods to simplify the use of Hotwire, having similar helper methods in Django would be beneficial.

You can explore django-turbo-helper, which provides helper functions to streamline working with Turbo in Django.

dom_id

dom_id is a helper method that generates a unique DOM ID based on the object’s class name and pk.

You can use it in Django template

{% dom_id instance %}               ->  task_1
{% dom_id instance 'detail' %}      ->  detail_task_1

The second parameter is optional, you can use it to add a prefix to the DOM ID.

Or you can also use it in Django view

from turbo_helper import dom_id

dom_id(instance)
# task_1

dom_id(instance, "detail")
# detail_task_1

We can say goodbye to id="task-{{ task.pk }}" and use id="{% dom_id task %}" instead.

The benefit is, it simplified the DOM ID generation on both sides, and avoid typo error in many cases.

Turbo Stream

We can reuse django template and generate Turbo Stream like this:

turbo_stream.update(        # action
  "dom_id",                 # target
  template="simple.html",
  context={"msg": "my content"},
  request=request
)

We can also do the same thing in Django template

{% turbo_stream 'update' 'dom_id' %}
  {% include 'simple.html' %}
{% endturbo_stream %}

django-turbo-helper is mainly to help developers detect Turbo relevant attributes in request and make it easier to use Turbo in Django.

Using it can improve our productivity on the server side.

Websocket

During the SaaS development, sometimes we need to build real-time features, for example: chat room, notification system, etc.

Websocket is good option in this case:

WebSocket is a communications protocol that provides persistent bi-directional communication channel between a client and server.

As Django Channels can help us make Django supports Websocket. However, there are still something missing on the frontend.

  1. Auto Connecting: how to auto reconnect when the client is disconnected.
  2. Multiplexing: can we use single one websocket connection for different channels?
  3. Client Side Code Structure: how to effectively organize the client side code better?

Fortunately, Rails provides a solution called ActionCable.

  1. ActionCable is a wrapper around WebSockets.
  2. On the client side, it provides a Javascript package which supports auto reconnect, multiplex, and client-side subscription model over the Websocket.
  3. The Javascript package is available on NPM https://www.npmjs.com/package/actioncable, which means we can use it with ANY backend server, not only Rails.

ActionCable is a well-established solution for WebSockets and has been extensively utilized in numerous Rails projects.

To support ActionCable on Django server, we can use django-actioncable, which is a lightweight wrapper of Django Channels to support ActionCable protocol.

You can check more details on Bring Rails ActionCable to Django

Things will become interesting when we transfer Turbo Stream over Websocket (via ActionCable), I will talk more in the next section.

Real-Time Page Updates with Hotwire

As you already know, we can use Turbo Stream to do DOM updates:

  • User submit a form or trigger an Ajax request to the server, the server return Turbo Stream to update the page, this happens in HTTP request-response cycle.
  • We can also transfer the Turbo Stream over Websocket, and broadcast the DOM updates to clients in async way.

Below is a video to show you how to do realtime partial page updates with Turbo Streams, you can also use Hotwire and Django to do the same thing.


We can even use distributed task queue solution such as Celery to broadcast the Turbo Stream for real-time page updates..

For example, once distributed worker completes generating a PDF report, the end user can receive a real-time toast notification, all this can be achieved without the need to write JavaScript.

To build similar feature with Django:

  1. To support turbo-cable-stream-source on the frontend, please make sure to install @hotwired/turbo-rails package, which is a wrapper of @hotwired/turbo but added some extra features such as turbo-cable-stream-source.
  2. Then follow Real Time Updates via Websocket
  3. The turbo-cable-stream-source implementation relies on ActionCable, so you can see after combing the Rails frontend techs together, we can make magic happen.

Extendable Turbo Stream Protocol

On the Turbo Stream Doc, we can see some builtin Turbo Stream actions such as Append, Prepend, etc.

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

What if we are not satisfied with the builtin actions but want to add some custom actions?

Turbo Stream is extendable

We can make Turbo Stream supports custom actions in this way

import { StreamActions } from "@hotwired/turbo"

StreamActions.log = function () {
  console.log(this.getAttribute("message"))
}

// <turbo-stream action="log" message="Hello, world"></turbo-stream>

This can let us define custom Turbo Stream actions, and trigger the action logic from the backend without writing Javascript. All we need is just using Django template to render some HTML.

You can check below packages:

  1. https://github.com/marcoroth/turbo_power
  2. https://github.com/hopsoft/turbo_boost-streams

For example, after installing the turbo_power to extend Turbo Stream actions on the frontend, we can add CSS class to DOM element by calling turbo_stream.add_css_class(target, classes) on the backend.

Since we can transfer Turbo Stream on both HTTP and Websocket, with extended Turbo Stream actions, we can do complex DOM updates from web server or distributed worker, without writing Javascript, this is so cool!

Reusable Server Side Component

With Stimulus, we can build reusable components on the client side, for example, if we have modal_controller.js, we can reuse the Javascript everywhere.

To import Modal component to the Django template, we need to add data-controller="modal" to the HTML element.

So in Django templates, we might have many HTML code like this:

<div data-controller="modal">
  <dialog data-modal-target="dialog">

  </dialog>

  <button data-action="modal#open">Open modal</button>
</div>

Since we are building SaaS business, it is highly likely we will update our Modal from time to time, here are some considerations:

  1. What if we want to change the style of the Modal component later?
  2. If we want modal to support new size, can we make the template code more clear without if else statement in the class attributes? (can we move some logic out of the template?)

To address these issues, it may be beneficial to explore server-side component solutions. Let's take a look at how the Rails community approaches this problem.

Rails ViewComponent

ViewComponent is a framework for building reusable, testable & encapsulated view components in Ruby on Rails, which is built by GitHub.com.

I strongly recommend you to check this video to know more details about ViewComponent first.


Many Rails projects have been using ViewComponent, if you want to check GitHub's server side components, please check:

https://primer.style/components, these are components for GitHub's Primer Design System, and they are currently used on GitHub.com.

Django-ViewComponent

Inspired by Rails ViewComponent, I built django-viewcomponent to do the same thing for Django.

Below is an example Modal component

from django_viewcomponent import component
from django_viewcomponent.fields import RendersOneField


@component.register("modal")
class ModalComponent(component.Component):
    modal_trigger = RendersOneField(required=True)
    modal_header = RendersOneField(required=True)
    modal_body = RendersOneField(required=True)
    modal_footer = RendersOneField(required=True)

    template_name = "modal/modal.html"

To use the component in Django template:

{% component 'modal' as modal %}
  {% call modal.modal_trigger %}
    <button data-action="click->modal#openModal" type="button" class="btn-blue" >Toggle modal</button>
  {% endcall %}

  {% call modal.modal_header %}
    <h3>Modal Title</h3>
  {% endcall %}

  {% call modal.modal_body %}
    <p class="text-base leading-relaxed text-gray-500">
      Lorem ipsum dolor sit amet, consectetur adipiscing elit. Integer in lectus et ipsum eleifend consequat. Aenean
      pellentesque tortor velit, non molestie ex ultrices in. Nulla at neque eu nulla imperdiet mattis vel sit amet
      neque.
    </p>
  {% endcall %}

  {% call modal.modal_footer %}
    <button data-action="click->modal#closeModal" type="button" class="btn-blue">Close</button>
  {% endcall %}
{% endcomponent %}
  1. With server side component, we transfer logic from the template to Python code, eliminating the need for if else statements in the class attribute.
  2. With the OO feature in Python, we can create a Python class for the Modal component, and we can easily extend it to support new features.
  3. Server-side components result in cleaner and more maintainable components and templates.

In this case, with server side Modal component, we can update it with minimal effort, and the changes will be applied to all templates that use the Modal component, which is convenient.

This approach can significantly reduce development time, allowing us to focus more on feature development.

Component Library

After building dozens or hundreds of the server side components, developers need a component library where they can manage components and check the relevant doc and usage.

For example, GitHub has component library https://primer.style/components, where you can search component, check the doc, and see the component usage.


Within the Rails community, there is a package called lookbook which can be used to create a component library, and it pairs very well with Rails ViewComponent


I have developed a ported version called django-lookbook to provide similar capabilities for Django users.

The below video showcases a user interacting with the button component, demonstrating its dynamic input capabilities.


django-lookbook can also be used to help you manage partial Django templates, you can check django-lookbook doc to know more.

Filling The Gap

The gap between Hotwire and Django has been bridged with the packages I have just mentioned.

  1. python-webpack-boilerplate: Brings modern frontend tooling to Django.
  2. django-turbo-helper: Provides helper functions for seamless integration of Turbo in Django.
  3. django-actioncable: Brings support for Rails ActionCable to Django.
  4. django-viewcomponent: Offers a server-side component solution for Django.
  5. django-lookbook: Provides a component library solution for Django.

Most of them are built by me in the past years, and my goal is to make Django "yet another one person framework".

Conclusion

Hotwire + Django is a powerful combination that offers a simple and productive way to build scalable SaaS.

The ecosystem around Hotwire provides a wealth of resources and tools to support developers in their journey on the frontend.

With Django's extensive documentation, rich ecosystem of third-party packages, and active community support, developers can leverage a wide range of tools to enhance their backend development.

Combining both technologies ensures that developers have the necessary resources to succeed in their SaaS projects and drive innovation in the constantly evolving landscape of web development.

Thanks for reading!

Michael Yin

Michael Yin

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

He has published some tech course on testdriven.io and ebooks on leanpub.

© 2024 SaaS Hammer