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
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>
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>
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>
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>
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>
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>
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
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
Top comments (0)