18 minutes read
The source code of the project can be found here.The web page that runs the application is located here. Notice: as I am occasionally changing things, the web page might be become inaccessible from time to time.
To start writing an htmx-powered web application we only need to link its sources in our web page. In our case, it will be the default index.html where the app starts.
The sources are part of this project, so that we simply link them locally, but one can receive them from CDNs as well. For example:
The htmx library augments the standard HTML controls in such a way that we can:
- use HTTP verbs other than GET/POST (why should we “delete” things with POST?)
- react to custom events (why should HTML only react to clicks/taps?)
- update parts of the HTML document (bye, bye “Virtual DOM”)
- let any control issue requests (why should only
<form>be able to issue GET / POST?)
Let’s see a few of those new “powers” in action, shall we? The web page of this project offers two buttons that look very basic.
But the HTML behind the blue button looks rather different from usual ones:
There are three hx-prefixed attributes:
With them, we declare that our button will issue a GET request against the given path, whereby the response from the server will be inserted into a target with a particular ID, and that the browser should swap target’s HTML with the one received.
With these three attributes we are able:
- to control the behavior of the button
- to communicate with the server and exchange data with it
- and on top of it, to control the visual representation on the client side
<form> tags. Nobody wants page refreshes after every button click.
Doing more with htmx
And this is why htmx exists. To augment HTML with new functionalities. Here’s another example that shows how we can send POST requests without using the
<form> control, which is still the only way to do it in the current HTML standard.
<form>, we now define a
<div> that contains a few
<input> fields and two
<button>s. This time, the second button contains two new attributes:
These two declare that clicking this button would issue a POST request and also include data from all <input> controls. We can select elements by the usual CSS selectors (class names, IDs, tags etc.). And just like that, without resorting to any imperative code for AJAX calls, we are able to issue requests and pass data together with them.
Using “forbidden” HTTP verbs
Let’s now try to do something that’s actually impossible with any known HTML control: issuing DELETE requests.
And again, we have a simple
<button> here that uses some new attributes:
And I am sure you already noticed, there is also a third, rather unusual attribute starting with an underscore that contains some kind of script. This is _hyperscript (no, the underscore is not a typo) and we will be talking about it shortly. But let’s first see what the two aforementioned attributes mean. hx-delete, as you have already guessed, is for issuing DELETE requests which are actually “impossible” inside browsers. However, htmx is here to extend the existing HTML, and there is really no reason why DELETE shouldn’t be possible. It’s part of the decades-old HTTP protocol. And HTTP was designed for HTML specifically.
Better interactivity with _hyperscript
_="on click remove #edit-c then remove me"
C++ web server with Drogon
So far, we have seen what is possible with HTML on the client-side when we use just these two libraries. Now, let’s see what can be done on the backend with C++ and my web server framework of choice, Drogon. The complete setup with all the needed libraries and packages are described in detail in the repo of this article, so there is no need to repeat myself. More important is the combination of a fast C++ framework, that is capable of processing web templates (Drogon calls them C++ Server Pages), together with a frontend-focused library like htmx and _hyperscript. This way we can achieve these important goals:
- server state doesn’t have to be replicated on the client
- no need for tricks like hydration
- no need for JSON APIs
- HTML becomes the true engine of the application state
In the C++ project folder
/src/templates you will find the view templates of the app:
C++ Server Pages
Drogon’s Server Pages contain bits of HTML code together with C++ code areas. In them, we can define any C++ code that will be executed when generating final C++ code. Here’s the complete code of the “Edit contact” CSP:
The code begins with an C++ area that is marked with <%c++ and %>. Everything in between is just regular C++ code and will be converted later into normal C++ code. It is also possible to embed it inside HTML, and run loops for example. Here is a snippet from the Contacts view.
The parsing of CSPs is done by drogon_ctl, a tool provided by Drogon framework. Its installation is described in the README of this project. Also, inside the HTML template we recognize a pattern like this:
- the server reacts to client requests by returning HTML pages
- those pages might be “enriched” with data that come from other sources like databases, files etc.
- and because those pages also contain htmx-enhanced controls, they will be able to provide client-side functionalities usually achieved by SPAs (Single-Page Applications)
Everything is in one page. In one language, HTML.
The next important piece of C++ logic are controllers. As we have already seen, this application is capable of issuing various HTTP requests. And somewhere on the other side, in the server, a piece of code should process them. For this we use Drogon’s server classes. There are two ways of declaring them, one is much quicker to be done, but not that powerful. It is based on
HttpSimpleController<T> type and can only define a single handler, like this:
But because we’re dealing with several non-trivial requests, we will be using regular HttpControllers
We declare our controller class by following the CRTP pattern and describe several routes and their respective handlers. Everything between
METHOD_LIST_END declares the accepted routes and HTTP verbs. It is also possible to define multiple verbs for the same route, like we did for
Contacts::list. The list of handlers below the
METHOD_LIST declares methods that will be called when a route gets hit.
In those handlers we can work with provided Requests and execute Response callbacks. We can query parameters, instantiate different Response classes, and communicate with our database (SQLite3 in this case, but it can be replaced with any other db system). Here’s the implementation of
Contacts::list that can react to GET and POST requests.
POST Request option:
First, we create instances of
DbManager, a simple class that takes care of all the CRUD stuff regarding the database, and
HttpViewData, a helper class that Drogon provides, which is used to collect and transport data from and to HTML documents. Then we check if there any parameters available. As the handler was called by a POST request, a parameter carrying the “first name” will be expected. Therefore, we search for it and create a “where clause” for the SQL query that
DbManager will later execute. The result will then be inserted into the
GET Request option:
The GET request option is much simpler as it merely instructs
DbManager to query for all available contacts and insert them into
HttpDataView. The rest is then the same as for the POST variant: an
HttpResponse will be generated. The two parameters of the factory method
newHttpViewResponse are the view name from the “templates” folder and the
The logic of all these handlers is following the same pattern:
- a request of certain type and with (optional) parameters comes in (GET, POST, DELETE)
- some processing will take place in the server (data querying, filtering, parameter handling etc.)
- a view representing the result will be generated and sent back to the client
I hope, that this work will be of some value to others. I will continue working on the app and the code will probably change a lot in the future. I will try to keep the article in-sync, or even create new ones.