adjoe Engineers’ Blog
 /  Backend  /  htmx Tutorial
abstract illustration with code elements
Backend

How to Simplify Web Development with htmx

In adjoe’s WAVE team, we continuously run A/B tests to measure the impact of our changes, evaluate the effectiveness of our bidding algorithm, or compare different user interfaces. 

Managing these tests previously involved manually setting up A/B tests through code adjustments and executing SQL queries. This process was prone to human error and quite cumbersome. It took a developer in our team up to a week to implement and launch one A/B test. 

To streamline this, we decided to develop a dashboard to create, manage, and monitor these experiments. Our primary requirement was to have a frontend interface intended exclusively for our internal teams, without the need for high levels of interactivity or performance. We aimed to build it as efficiently as possible with our available resources.

Which Framework Did We Choose?

React is the most popular framework for building frontends; we initially considered it due to its vast ecosystem of component libraries and tools. However, we wondered if an SPA framework like React was necessary. 

We also considered alternatives like jQuery or Vanilla JS. However, we ultimately chose htmx for its simplicity, maintainability, and excellent compatibility with Go. This choice allowed us to reduce development time. 

Setting up htmx is also straightforward. You only need to add a script tag to include the dependency – this is a sharp contrast to the complicated build systems common in other frontend technologies. The ability to use a common language across both the frontend and backend was a huge advantage and strongly influenced our decision to choose htmx. 

This led us to discover its advantages, which I will explore further in this article.

How Is Building with htmx Different?

Let’s first review the history of web technologies to better understand the htmx approach to web development.

Multi-Page Applications (MPAs)

Websites were initially primarily multi-page applications (MPAs) that interacted with servers in two main ways. 

The anchor tag first instructed the browser to fetch a new page via an HTTP GET request and replace the current page with the response. HTML forms then permitted data submission to the server using an HTTP POST request, which often redirected users to a new page afterward. 

These interactions were simple and straightforward; the server responded directly with the HTML to be rendered, but also required the entire page to be refreshed with each interaction. Native HTML was also limited to only sending HTTP GET and POST requests. This limitation led to the adoption of JavaScript to enhance user experiences and interactivity.

Single-Page Applications (SPAs)

When it comes to single-page applications (SPAs), server interactions have changed significantly. In SPAs, clicking a button might trigger an HTTP request of any method, and servers typically respond with a JSON object. 

The JavaScript running in the browser uses this JSON to update the state of a domain model maintained internally. This model then refreshes the relevant parts of the webpage, allowing for a more interactive user experience.

However, this method requires the client to understand the exact structure of the JSON response and its meaning. The client must recognize which further actions are valid and available, given the data provided. It must know how to render this data to the UI.

Additionally, the client needs to be updated whenever there are changes to the API. While the SPA approach enables a much more interactive and richer user experience, it comes at the cost of increased complexity.

Hypermedia-Driven Applications (HDAs)

Hypermedia-driven applications built with htmx, on the other hand, extend the capabilities of HTML while preserving the simplicity of the MPA approach. 

htmx generalizes the capabilities of standard HTML, enabling any HTML element to initiate HTTP requests of any method through simple attributes. The requests can also be triggered in response to a variety of user interactions beyond traditional mouse clicks and submit events. Critically, htmx facilitates updating only specific parts of a webpage – targeting and replacing subsets of the HTML DOM.

Since rendering occurs on the server, the client does not require additional logic or knowledge about which states are valid for which entities. The server directly returns the valid state along with interactions that are available in that state of the system, and the browser renders it. 

With these features, HDAs maintain the straightforward architecture of traditional web technologies while facilitating the creation of interactive user experiences like those found in modern SPAs. 

graphic of htmx and how it has worked since 2004
Source: htmx.org

How Does the Dashboard Work?

The core feature of our dashboard was an experiment creation wizard. This wizard enabled users to create and modify various attributes of an experiment using various forms. A typical recurring pattern that we used can be seen below.

<form hx-put={ fmt.Sprintf("/experiments/%s/information", experiment.ID) } hx-target-error="#notifications" hx-swap="innerHTML">
   // Input fields
   <button>Next</button>
</form>
<div id="notifications"></div>
screenshot of what htmx dashboard looks like in the browser

Here, we utilized the hx-put attribute to instruct htmx to send an HTTP PUT request to the specified URL. This carried form data within the request body. 

We also used the hx-target and hx-target-error attributes to manage the server’s response. If the server returns a success status, the response HTML replaces the content specified in hx-target. 

For error responses, such as a 500 status code, the HTML updates the area defined by hx-target-error, which is convenient for displaying validation errors to the user. The hx-swap attribute defined how the response would replace the target. Options included appending to the target or replacing its inner HTML, among other modes.

In the example above, we did not use an hx-target because, upon successful form submission, we redirected the user to the next page of the form using the “HX-Redirect” header in the response.

We also found htmx to be effective for building the group creation page. This is a key part of our dashboard that manages the distribution of different treatments among user groups. This page featured an interactive table that enabled users to easily add, update, or delete rows.

templ GroupsTable(experiment ui.Experiment) {
   <div>
      <label>Experiment Groups</label>
      <ul>Column headers</ul>
      <ul id="groups">
         for _, group := range experiment.Groups {
            @GroupRow(group, editable)
         }
      </ul>
         <button hx-post={ fmt.Sprintf("/experiments/%s/groups", experiment.ID) } hx-target="#groups" hx-swap="beforeend">Add</button>
   </div>
}


templ GroupRow(group ui.Group, editable bool) {
   <li>
      // Editable Columns
         <button hx-delete={ fmt.Sprintf("/experiments/%s/groups/%s", group.ExperimentID, group.ID) } hx-target="closest li" hx-swap="outerHTML">Delete</button>
   </li>
}
screenshot of what experiment update interface looks like in browser

The GroupsTable component, as seen here, is part of a form that uses htmx to enhance functionality, similar to the pattern described earlier. The “Add” button within the table initiates a POST request, which returns the HTML for a new group row. 

This new row is then appended to the end of the list, thanks to the hx-swap=”beforeend” attribute. This instructs htmx to add the response HTML at the table’s end.

Each row in the table includes a “Delete” button that, when activated, does not return any HTML. Instead, the htmx attribute hx-target specifies that the closest li element — the row itself — should be removed. 

This functionality demonstrates htmx’s capability to target HTML elements dynamically using not just CSS selectors like the id tag, but also selectors such as closest, next, and previous. This setup showcases the simplicity and effectiveness of creating an interactive table with htmx, eliminating the need for additional JavaScript. 

Using just a few patterns like these, we successfully implemented the entire experiment management dashboard without any JavaScript. We also successfully deployed it thanks to htmx’s simplicity. It proved to be a perfect fit for our requirements. 

Choosing htmx allowed us to deliver a more convenient and reliable way to manage our experiments. This resulted in tighter iteration loops and quicker product improvements – whether for the UI that users interact with while watching in-app ads or the algorithms used for bidding on ad inventory.

When Should You Use htmx?

htmx is well-suited for scenarios where:

  • your application requires minimal to moderate interactivity
  • offline functionality is not a requirement
  • the core value of your application is derived from backend logic and server-side validation
  • the UI state does not require frequent updates

Its ability to deliver server-driven interactivity with minimal client-side code makes it a versatile and effective solution for building rich user experiences.

Of course, you can use htmx for some parts of your application while also integrating it with more interactive frameworks like React for other parts that demand greater interactivity. Choosing the right tool for the job is essential, and htmx offers a great option for web development where speed and simplicity are priorities.

Senior QA Engineer (f/m/d)

  • adjoe
  • Programmatic Supply
  • Full-time
adjoe is a leading mobile ad platform developing cutting-edge advertising and monetization solutions that take its app partners’ business to the next level. Part of the applike group ecosystem, adjoe is home to an advanced tech stack, powerful financial backing from Bertelsmann, and a highly motivated workforce to be reckoned with.

Meet Your Team: Programmatic Supply
In a competitive adtech market, adjoe stands for greater transparency and fairness for app publishers and advertisers – and a more relevant and enjoyable experience for users. It’s exactly for this reason that adjoe’s Programmatic team has built its own mediation platform WAVE. It’s not only driven by a backend application that decides which ad to show but also by our Android and iOS SDKs. These render the ads for the user and provide useful tracking events to the backend. The WAVE SDK serves millions of in-app ads a day in different formats, and our backend system handles a few billion auctions in real time every day to help game developers monetize their apps. Want to be a part of this industry-first adtech solution? Join our discussions, explore implementation, and put your problem-solving skills to the test in our cross-functional Programmatic team!
What You Will Do:
  • Conduct manual tests of our product and analyze results to ensure the correct implementation of its features and experiments
  • Play a key role in reporting any bugs or errors that may occur
  • Take ownership of post-release and post-implementation testing
  • Collaborate with your leads and other QA engineers to develop and execute comprehensive test strategies and plans
  • Have an opportunity to collaborate with other QA engineers on automated tests in the future
  • Contribute to product-related decisions based on your insights
  • Who You Are:
  • You have a degree in information technology, economics, analytics, or a similar field
  • You have 2+ years’ experience as a QA engineer
  • You have experience testing not only web/mobile applications, but also complicated backend systems using tools such as Kreya, Postman or k9s
  • You have worked with and tested technologies such as container orchestration systems (e.g. Kubernetes, Docker), databases (e.g. MySQL, Redis, Scylla) or messaging systems (e.g. AWS SQS, Apache Kafka) or you know in which context they can be used
  • You have experience in working closely with engineers (investigating how the system works, discussing feature requirements)
  • You are experienced in QA methodology and different types of testing (regression/smoke/etc)
  • You’re comfortable reading and writing tech documentation
  • You have excellent communication & organizational skills
  • Plus: experience in autotesting
  • Heard of Our Perks?
  • Work-Life Package: 2 remote days per week, 30 vacation days, 3 weeks per year of remote work, flexible working hours, dog-friendly kick-ass office in the center of the city.
  • Relocation Package: Visa & legal support, relocation bonus, reimbursement of German Classes costs, and more.
  • Happy Belly Package: Monthly company lunch, tons of free snacks and drinks, free breakfast & fresh delicious pastries every Monday
  • Physical & Mental Health Package: In-house gym with a personal trainer, various classes like Yoga with expert teachers & free of charge access to our EAP (Employee Assistance Program) to support your mental health and well-being
  • Activity Package: Regular team and company events, and hackathons.
  • Education Package: Opportunities to boost your professional development with courses and training directly connected to your career goals 
  • Wealth building: virtual stock options for all our regular employees
  • Skip writing cover letters. Tell us about your most passionate personal project, your desired salary and your earliest possible start date. We are looking forward to your application!

    We welcome applications from people who will contribute to the diversity of our company.

    We’re programmed to succeed

    See vacancies