18 minutes read
In this article we will learn how to build a responsive web app with htmx, Drogon C++ web framework, and no JavaScript at all. Yes, that’s true, neither hand-written JavaScript nor TypeScript. Also no frontend frameworks like Angular, React etc. The app offers the usual CRUD functionalities, like viewing, adding, changing, and deleting data. The database is SQLite3 and the backend is responsible for processing requests and generating HTML.
But before we start, let me say that this article isn’t about bashing JavaScript and/or frontend frameworks. I’m a web dev too, and JavaScript is here to stay. For many years. What is about to change, in my opinion, is the way we will be looking at it in the future. Instead of using JavaScript to replace HTML, and hypermedia in general, it will be used to augment it. Instead of writing complex frontend clients that basically mimic fat-clients from the Nineties, we will be using lightweight, composable, and event-driven HTML controls. Of course, there will be cases that demand hand-crafted components, but most of them will be based on “default” HTML controls. And even then, we will rarely need to use JavaScript for customization. Instead, more declarative languages will become our first choice as they simply fit more nicely with the declarative nature of Hypermedia, that is HTML.
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.
This work is based on the excellent book “Hypermedia Systems” that will be published soon. You can read the free online version. I myself used it to build this project and their original Python sources inspired me to try the same with C++. In case you might be asking “why C++?”, let me say that I not only think that JavaScript isn’t the only way to write responsive web apps, but also that C++ is a perfectly fine language for web applications. It really depends on our understanding of technology and how we plan to use it to deliver successful products. Yes, the web is filled with tutorials on how to build web apps in less-than-10-minutes with .NET, JVM, Python, Ruby etc. But this doesn’t mean that there are no other, equally powerful and useful solutions. By following the more modern C++ rules and recommendations, equipped with a versatile framework like Drogon, there is nothing that prevents us to setup a web server that is as capable as any other from the .NET, JVM, or Python world. And I am by no means an experienced C++ developer. I have never developed a C++-based web app before, but look, here it is. And I hope, it will motivate others to try something new with htmx.
Setup
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.
<script src="vendor/htmx.min.js"></script>
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:
<script src="https://unpkg.com/htmx.org@1.8.4"></script>
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
<a>
and<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.
This, of course, is a complete departure from the “usual” way of creating web apps. In almost every frontend framework out there, we would have one part, written in JavaScript, that takes care of updating the browser DOM, thus completely circumventing HTML, and some other logic that communicates with the server via AJAX and JSON APIs (which btw. are anything but RESTful, but this is something the Hypermedia Systems book can explain much better than I).
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
And we did not have to write any JavaScript at all. Why? The answer is simple: htmx is enhancing the standard HTML by injecting new capabilities into it. And to use them, we only need to enter proper attributes. This way HTML can be made capable of doing things directly, without taking a detour through JavaScript. Actually, it’s not even a detour, but a total circumvention of HTML that is happening today in most of the frontend frameworks.
Why do we need so much JavaScript in the browser?
What current frontends do is basically defeating HTML altogether by providing new runtimes that take care of syncing the state between servers and clients. Every time someone changes data in the client, the frontend is obliged to keep the information on both sides in sync, and also to take care of adding or removing any visible elements that reflect those actions. Think of any non-trivial application that uses several tables. Anytime something happens in the client that should also be known by the web server, the frontend would have to issue an AJAX request against some kind of JSON API. The data that goes between client and server must be reflected properly on both sides. In the server this usually means changing some database entries. And on the client this usually leads to some visual changes, like new elements being inserted and/or removed through JavaScript-based HTML element manipulation. But all that is neither Hypermedia nor it is RESTful. JSON has no semantic meaning. And HTML controls can’t do anything with JSON or any other format. This is why we need JavaScript to take care of gluing stuff together and “translating” between disparate worlds. But these worlds are because of our own choosing. There exists an alternative since the beginning of the web. The Web itself. HTML and HTTP.
But instead of using HTML as the engine for our web apps, we effectively circumvent it. However, I am not attacking JavaScript for that, as there is a reason why all these solutions exist today. The HTML as we know it remained pretty much the same since mid-Nineties. No wonder, people had to develop frameworks to help them do more than merely dealing with <button>
, <a>
and <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.
Instead of <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.
HTML is not something that developed over a couple of millennia through natural, evolutionary processes and mutations. It’s human-made and therefore it should be extensible, and extended when there is a need for change. In fact, JavaScript itself started as a simple language for “adding interactivity to web sites”. You know, button clicks, animations, pop-ups. Who could have thought of complex frontend frameworks powered by JavaScript back then? But I digress here, so let’s continue with the other attribute: hx-confirm. Its task is to display a confirmation windows asking the user if the request should be sent. As we are deleting data here, such confirmations should appear before, shouldn’t they?
Better interactivity with _hyperscript
As already mentioned, we are not only enhancing HTML with htmx but also with _hypescript, which is a language specifically designed for it. It is highly declarative and integrates itself with HTML. Unlike JavaScript, that can never be made declarative and thus always feels like a second-class citizen inside HTML, _hyperscript becomes part of HTML. With it we can use anything HTML provides: all events, all CSS selectors, all attributes. If we look at the piece of code from the above example, we can immediately understand what its task is:
_="on click remove #edit-c then remove me"
After the containing element got clicked, the HTML element with the ID #edit-c will be removed, and then the containing element will be removed too. This is just a small example, but it hopefully shows how much can be achieved with just a little bit of declarative code. If still unconvinced, then please, try to achieve the same with JavaScript. You’d have to write listeners that run completely outside of HTML of course. The important part of all this is that we don’t need to leave HTML to achieve the same interactivity. It’s built-in.
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:
They contain bits of HTML code that the server will be sending to clients according to their current state. That is, when, for example, the client clicks “Add contact”, the server will send back a piece of HTML that contains everything needed to create a contact. All the buttons, all the fields, everything. And client’s only job will be to replace parts of its current HTML according to its rules. We saw them in previous examples with hx-target. And when the user completes the task by clicking “Save”, the client will send data from input-fields back to the server. And hopefully, you’ve already spot the difference. Instead of calling some JSON API from the client and updating HTML by using a separate, JavaScript-based logic, this application’s state is server state. There is no need to use parallel mechanisms like JSON APIs to keep the states in sync. The only medium that is needed is HTML itself. This is the meaning of HATEOAS (Hypermedia As The Engine Of Application State).
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:
{%contact.FirstName%} resembles the popular “mustache” pattern that is well-known in the JavaScript world. Here, we declare, that initial values of input fields will be taken from certain C++ class properties. The logic of the whole htmx+_hyperscript+C++ is hopefully now more clear:
- 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.
Drogon Controllers
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_BEGIN
and 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 HttpViewData
instance, which in turn would be used to change the structure of the HTML document that will be sent back to the client. This is the “search” functionality of the app that can be seen in the video at the beginning on this article. Whenever the user enters data in the “Search” input field and clicks the button, the parameters will be sent to this handler. This is how simple a “dynamic” search can actually be. No client-side JavaScript needed.
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 HttpViewData
instance.
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
As already discussed, there is little need for explicit JavaScript on the client side. Most of the “intelligent” processing will take place on the server. And to setup a responsive web app on the client side, we can easily include the htmx library, and optionally, _hyperscript.
I am not saying that SPAs will go away any time soon, but we should really think twice before using this or that frontend frameworks for our next web app. Many seemingly complex things can be achieved by using htmx and _hyperscript. The app I described here is by no means comparable with complex frontend SPAs, but this is not the important part. What’s important is, how little was actually needed to make the frontend behave similar to responsive JavaScript apps. Even without a real backend framework, the functionalities and capabilities achieved by mere linking of htmx and _hyperscript libraries are reason enough for me, to rethink frontend development.
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.
5 thoughts on “Writing HDAs with htmx and C++”
Héi great coding, congratulations ! I need soma help that …
[8/14] Compiling C++ object test_demo_web_server.p/test_test_demo_web_server.cpp.o
[9/14] Linking target test_demo_web_server
FAILED: test_demo_web_server
c++ -o test_demo_web_server test_demo_web_server.p/test_test_demo_web_server.cpp.o test_demo_web_server.p/src_server_server_config.cpp.o -Wl,–as-needed -Wl,–no-undefined ” -Wl,–start-group /opt/vcpkg/packages/drogon_x64-linux/lib/libdrogon.a /opt/vcpkg/packages/trantor_x64-linux/lib/libtrantor.a /opt/vcpkg/packages/jsoncpp_x64-linux/lib/libjsoncpp.a /opt/vcpkg/packages/openssl_x64-linux/lib/libssl.a /opt/vcpkg/packages/openssl_x64-linux/lib/libcrypto.a /opt/vcpkg/packages/brotli_x64-linux/lib/libbrotlicommon-static.a /opt/vcpkg/packages/brotli_x64-linux/lib/libbrotlienc-static.a /opt/vcpkg/packages/brotli_x64-linux/lib/libbrotlidec-static.a /opt/vcpkg/packages/c-ares_x64-linux/lib/libcares.a /opt/vcpkg/packages/zlib_x64-linux/lib/libz.a /opt/vcpkg/packages/fmt_x64-linux/lib/libfmt.a /opt/vcpkg/packages/sqlite3_x64-linux/lib/libsqlite3.a /opt/vcpkg/packages/soci_x64-linux/lib/libsoci_core.a /opt/vcpkg/packages/soci_x64-linux/lib/libsoci_sqlite3.a -lresolv /usr/lib/x86_64-linux-gnu/libcriterion.so -Wl,–end-group
c++: error: : Arquivo ou diretório inexistente
[10/14] Compiling C++ object demo_web_server.p/src_controllers_home_home.cpp.o
…
ninja: build stopped: subcommand failed.
i am using Linux srv 6.0.0-6mx-amd64 #1 SMP PREEMPT_DYNAMIC Debian 6.0.12-1~mx21+1 (2022-12-15) x86_64 GNU/Linux
is what wrong ?
I am not offering support here. This is just a blog. Also, the error messages are not in English, so I don’t event understand what they mean. I assume some files are missing (“Arquivo ou diretório inexistente”). Check if you have installed all packages. For more precise help, check the docs and github repos of your C++ compiler, vcpkg, and other tools you are using.
Thank you !
Would like to see more articles written about drogon.
Thankyou for the htmx library, and _hyperscript language, I was searching for a front-end to compliment drogons CSP system.