Use HTMX with Spring Boot and Thymeleaf

yosephtenaw

Yoseph Tenaw

Posted on April 6, 2024

Use HTMX with Spring Boot and Thymeleaf

Image description

So, you want to create a front-end for your app, and you're gearing up for the next step. These days, it most likely means considering one of the three horsemen of JavaScript frameworks: React, Vue, and Angular... and perhaps Svelte (which I've heard is pretty alright). If just the thought of this phase of development exhausts you, you're likely suffering from JavaScript fatigue. This is when everywhere you turn, it's JS! and you're just bored or tired of it. What makes it even more exhausting is when you have to use a JS framework to create a simple admin page that could have just been an SQL query, to be honest.

If you're anything like me, you'd probably try to sidestep this situation by using server-rendered pages and just hurling HTML at the browser on every request. Even though this might seem a bit crude, I'm okay with it, but management might not appreciate the lack of some interactivity. So... is there a way to do most of the rendering on the server and add that little bit of reactivity to the page without succumbing to the Deception of React, the Temptation of Vue, and the Biblical Leviathan that is Angular?"

"Come to me, all you who labor under the weight of JavaScript, and I will give you rest. For my use case is easy, and my burden is light." - HTMX

In this blog we will be making a full-stack app (without DB) using HTMX, server side templating engine in Thymeleaf and Spring Framework for the backend. I hope you enjoy it. Full code can be found here

What is HTMX

From the official site, "Htmx is a library that allows you to access modern browser features directly from HTML, rather than using javascript"
So basically HTMX is JS library (How ironic), that enhances your page to access things HTTP requests and events directly from HTML tags. You can use these handles to initiate actions and react to events without a single code of JavaScript. Pretty cool. You can read more about this on their documentation page here.

What are Spring Boot & Thymeleaf

Spring Boot is a Java-based framework that provides a streamlined approach to setting up and configuring Spring-based applications, reducing the burden of manual setup and allowing developers to focus on writing business logic rather than dealing with infrastructure concerns.

Thymeleaf, on the other hand, is a modern server-side Java template engine that enables developers to create dynamic and interactive web pages. It seamlessly integrates with Spring Boot, allowing developers to easily generate HTML content using natural and readable syntax, while also providing powerful features for templating and data binding.

Why do this? (For the haters)

In an era where Java seems to fall out of favor with many developers and JavaScript frameworks overwhelm us daily, undertaking this endeavor is akin to a rebellion against the dominance of frameworks and a light jab at those who dismiss Java

How to do this

Now that we got all the concepts and biases out of the way, lets get to implementing. You can follow along with the below steps or just go straight to the repo here. The first step is:

Setup

As with every spring boot app it's easier to go to the spring initializer site (https://start.spring.io/) as select the starter dependencies and other configs. For this project select the following dependencies

  • Thymleaf (this is our templating engine, renders our HTML)
  • Spring Web (this is essential to build web applications)
  • Spring Boot Dev tools (For live reloads, this is optional)

Since I want this blog to be focued on the front-end part of the application you can implement/see the controllers and configurations your self by going to this repository

Create HTML Pages

In the previous step we have laid out the functions/endpoints that our web app serves. Now lets see what it serves. Spring boot, with default configuration, responds with application/json response type. But what we want is for it respond to our request with HTML, so to help us with this, we have added Thymeleaf in our dependencies list. When adding Thymeleaf it auto-configures Spring Boot to respond with HTML files and fragments inside our resources/templates.

Lets create the HTML files

resource/templates
-- dashboard.html
-- login.html
-- fragments/
---- core.html
---- dialog.html
---- head.html
Enter fullscreen mode Exit fullscreen mode

N.B: Fragments are html snippets that can be returned by the server. Think an isolated <div th:fragment='frag'>..</div> this is a fragments named frag

Now lets build our HTML pages and fragments with HTMX included

//head.html
<head th:fragment="head">
  <meta charset="UTF-8" />
  <meta name="viewport" content="width=device-width, initial-scale=1.0" />
  <title>HTMX Admin Panel</title>
  <link
    rel="stylesheet"
    href="https://cdn.jsdelivr.net/npm/bootstrap@5.3.2/dist/css/bootstrap.min.css"
    integrity="sha384-T3c6CoIi6uLrA9TneNEoa7RxnatzjcDSCmG1MXxSR1GAsXEV/Dwwykc2MPK8M2HN"
    crossorigin="anonymous"
  />
  <script
    src="https://cdn.jsdelivr.net/npm/bootstrap@5.3.2/dist/js/bootstrap.min.js"
    integrity="sha384-BBtl+eGJRgqQAUMxJ7pMwbEyER4l1g+O15P+16Ep7Q9Q+zqX6gSbd85u4mG4QzX+"
    crossorigin="anonymous"
  ></script>
  <script
    src="https://unpkg.com/htmx.org@1.9.11"
    integrity="sha384-0gxUXCCR8yv9FM2b+U3FDbsKthCI66oH5IA9fHppQq9DDMHuMauqq1ZHBpJxQ0J0"
    crossorigin="anonymous"
  ></script>
  <style>
    .loginform {
      display: flex;
      flex-direction: column;
      padding: 1em;
    }
    .form-cont {
      width: 50%;
    }
    .loginbody {
      padding: 4em;
    }
  </style>
</head>

Enter fullscreen mode Exit fullscreen mode

Our first fragment is the head fragment found in the head.html
It loads up our bootstrap CSS and JS files, and most importantly the HTMX library which will allow to do some cool stuff. This fragment will be included in our main dashboard.html and login.html files.

// login.html

<!DOCTYPE html>
<html xmlns:th="http://www.thymeleaf.org">
  <head th:replace="~{fragments/head ::head}"></head>
  <body class="loginbody">
    <div class="m-auto form-cont">
      <h1>Login</h1>
      <form action="/login" method="post" class="loginform">
        <input
          type="text"
          name="username"
          id="username"
          placeholder="Username"
          class="mb-2"
        />
        <input
          type="password"
          name="password"
          id="pass"
          placeholder="Password"
          class="mb-2"
        />
        <button class="btn btn-primary" type="submit">Login</button>
      </form>
    </div>
  </body>
</html>

Enter fullscreen mode Exit fullscreen mode

This a regular HTML being rendered by Thymeleaf, we used the attribute th:replace to import a fragment and apply it in this html. This page is the login page, it send login details to the /login endpoint.

//dashboard.html
<!DOCTYPE html>
<html lang="en">
  <head th:replace="~{fragments/head ::head}"></head>
  <body>
    <div class="container">
      <div>
        <h1 class="mb-4 mt-4">Dashboard</h1>
        <button class="btn btn-sm btn-info" hx-post="/logout">Log Out</button>
        <div class="d-flex justify-content-between">
          <h2 class="mb-4">People</h2>
          <button
            class="btn btn-primary btn-xs"
            hx-get="/refresh"
            hx-target="table"
          >
            Refresh
          </button>
        </div>
        <table class="table table-striped" style="table-layout: fixed">
          <thead>
            <td>Id</td>
            <td>Name</td>
            <td>Description</td>
            <td>Action</td>
          </thead>
          <tbody id="main_table">
            <tr th:id="'id_' + ${row.id}" th:each="row : ${rows}">
              <td th:text="${row.id}"></td>
              <td th:text="${row.name}"></td>
              <td th:text="${row.desc}"></td>
              <td>
                <button
                  class="btn btn-danger"
                  th:hx-delete="'/delete?id=' + ${row.id}"
                  th:hx-target="'#id_' + ${row.id}"
                >
                  Delete
                </button>
                <button
                  class="btn btn-primary"
                  data-bs-toggle="modal"
                  data-bs-target="#editModal"
                  th:hx-get="'/edit-form?id=' + ${row.id} +'&name='+ ${row.name} +'&desc='+ ${row.desc}"
                  hx-target="#edit-dialog-body"
                  hx-trigger="click"
                >
                  Edit
                </button>
              </td>
            </tr>
          </tbody>
        </table>
        <button
          class="btn btn-primary"
          data-bs-toggle="modal"
          data-bs-target="#createModal"
        >
          Add New
        </button>
      </div>
    </div>
    <div th:replace="~{fragments/dialog :: createmodal}"></div>
    <div th:replace="~{fragments/dialog :: editmodal}"></div>
  </body>
</html>

Enter fullscreen mode Exit fullscreen mode

This is our dashboard page, it is first rendered by Thymeleaf.
The th:each="row : ${rows}" attribute helps iteratively render HTML elements, in this case it loops over the rows variable passed from Spring boot and replacing the contents of children with th:text property.

N.B: If an attribute has a th: attached in front of any html attribute, it will process the ${} inside the attribute. For example <td th:text="${row.desc}"></td> when return from the server row.desc will be replaced/processed by thymeleaf into an actual value.

With this in mind lets talk about the htmx attributes. In this page we use: hx-get hx-delete and hx-target
we used hx-get in the line

 <button
  class="btn btn-primary"
  data-bs-toggle="modal"
  data-bs-target="#editModal"
  th:hx-get="'/edit-form?id=' + ${row.id} +'&name='+ ${row.name} +'&desc='+ ${row.desc}"
  hx-target="#edit-dialog-body"
  hx-trigger="click"
 >Edit</button>

Enter fullscreen mode Exit fullscreen mode

After render, you can ignore the th: attribute and replace the ${} with actual values. What this htmx attribute will do is send a GET request to /edit-form endpoint and replace an HTML element that has an id of edit-dialog-body(found in another fragment) with the returned html value. And this all is triggered by an click on the button.

<button
 class="btn btn-danger"
 th:hx-delete="'/delete?id=' + ${row.id}"
 th:hx-target="'#id_' + ${row.id}"
>
 Delete
</button>
Enter fullscreen mode Exit fullscreen mode

When this button is clicked, it will send a DELETE request to the /delete endpoint and it will remove an html element that has an id specified in the hx-target attribute

        <form
          hx-post="/add"
          hx-target="#main_table"
          hx-swap="beforeend"
          class="d-flex flex-column"
        >
          <input type="text" name="id" placeholder="Id" class="mb-2" />
          <input type="text" name="name" placeholder="Name" class="mb-2" />
          <input
            type="text"
            name="desc"
            placeholder="Description"
            class="mb-2"
          />
          <button
            type="submit"
            class="btn btn-sm btn-primary"
            data-bs-dismiss="modal"
          >
            Add
          </button>
        </form>
Enter fullscreen mode Exit fullscreen mode

In the createmodal fragment we have a form that is used to create an entry. It sends a POST request to /add endpoint when the form is submitted. When it receives a response to will grab the table (#main_table) and it appends the responded HTML (which is a row fragment); this is why we used beforeEnd on hx-swap attribute

These are the main things about htmx mostly. You can take a look at the repository for the full code and more explanations

Testing it out

This is what it is supposed to look like after its done

Finished

My Two Cents

Alright, now let's get down to the facts and caveats. While I appreciate HTMX and the simplicity it brings, there are certain tasks that are considerably more challenging to accomplish with it. In those cases, you're better off sticking with an established framework. Every website is not a simple informational screen - most of the time it requires heavy interactivity. However, if you find that a full-fledged JS framework is overkill for your basic internal site, for instance, I strongly encourage you to give HTMX a try.

Stay safe guys
Yoseph Tenaw

💖 💪 🙅 🚩
yosephtenaw
Yoseph Tenaw

Posted on April 6, 2024

Join Our Newsletter. No Spam, Only the good stuff.

Sign up to receive the latest update from our blog.

Related