This repository contains two projects. The first gathers the Datastar examples, providing a practical showcase of how the DSL can be used in real backend-driven web applications. The second implements an equivalent version of the classic Spring Petclinic by replacing the Thymeleaf template engine with the Type-Safe Hypermedia-First DSL for Reactive Backend-Driven Web Applications.
🚀 DataStar version: 1.0.1
This project includes a demo web application featuring examples from Data-Star, running on Ktor and http4k and using the HtmlFlow Kotlin DSL to generate HTML.
HtmlFlow DSL provides type-safe backend handlers for DataStar actions.
In the following samples, note how the action get is attached to a handler given by a function reference, this function being annotated
with Path, from Jakarta, where the resource location for the
request is specified.
HtmlFlow DSL also provides a type-safe way to define Datastar attributes in Kotlin. Next is a sample of the Counter example with a strongly typed signal and another one from Active Search using events and modifiers:
div {
val count: Signal = dataSignal("count", 0)
div {
dataInit { get(::getCounterEvents) }
span {
attrId("counter")
dataText{ +count }
}
}
} |
div {
attrId("demo")
input {
attrType(EnumTypeInputType.TEXT)
attrPlaceholder("Search...")
dataBind("search")
dataOn(Input) {
get(::search)
modifiers { debounce(200.milliseconds) }
}
}
} |
Note that the count variable is of type Signal
and simply binds to the data-text in a type-safe way,
regardless of the name passed to the Signal constructor.
|
Modifiers such as debounce are added inside a lambda using
builders, following an idiomatic Kotlin style.
|
Also, HtmlFlow DSL supports strongly typed DataStar expressions,
allowing their composition with the infix operator and,
such as in the expression !fetching and get(::clickToLoadMore).
Note how the JavaScript expression for the onclick event handler (right side)
is expressed in Kotlin through HtmlFlow in a type-safe way:
button {
val fetching = dataIndicator("_fetching")
dataAttr("disabled") { +fetching }
dataOn(Click) {
!fetching and get(::clickToLoadMore)
}
text("Load More")
} |
<button
data-indicator:_fetching
data-attr:aria-disabled="$_fetching"
data-on:click="!$_fetching && @get('/examples/click_to_load/more')"
>
Load More
</button> |
Change to the datastar-examples directory and run the application with Gradle. The application will start two servers, one for Ktor and another for http4k, each running the same examples.
cd ./datastar-examplesRun with:
./gradlew runThen open http://localhost:8080 for the ktor server and http://localhost:8070 for http4k in your browser.
Check all examples from the index page and corresponding HtmlFlow view definitions:
- Active Search - ActiveSearch.kt
- Bulk Update - BulkUpdate.kt
- Click To Edit - ClickToEdit.kt
- Click To Load - BulkUpdate.kt
- Counter Via Signals - CounterViaSignals.kt
- Delete Row - DeleteRow.kt
- File Upload - FileUpload.kt
- Infinite Scroll - InfiniteScroll.kt
- Inline Validation - InlineValidation.kt
The petclinic-htmlflow module contains an implementation of the Spring Petclinic application using HtmlFlow Kotlin views and DataStar hypermedia controls; it replaces the previous Thymeleaf templates. The goal is to preserve the original Petclinic domain and features while exploring a backend-driven, hypermedia-first UI model that is:
- Type-safe: views are expressed in Kotlin using the HtmlFlow DSL.
- Server-driven: DataStar actions and signals enable server-initiated UI updates.
- Incremental and efficient: updates can patch page fragments (for example table rows) instead of reloading whole pages.
Run the Petclinic application with Gradle:
cd ./petclinic-htmlflow
./gradlew bootRunOpen http://localhost:8080 in your browser. The application supports the usual Petclinic features (owners, pets, visits) implemented with HtmlFlow/DataStar rather than Thymeleaf.
Below is a simplified comparison showing the traditional Thymeleaf findOwners form and an equivalent HtmlFlow view that uses data binding and a Debounce modifier to drive the search results from the server.
Thymeleaf (classic form)
<h2>Find Owners</h2>
<form th:object="${owner}" th:action="@{/owners}" method="get">
<div id="lastNameGroup">
<label>Last name</label>
<div>
<input th:field="*{lastName}" size="30" maxlength="80"/>
<span class="help-inline">
<div th:if="${#fields.hasAnyErrors()}">
<p th:each="err : ${#fields.allErrors()}" th:text="${err}">Error</p>
</div>
</span>
</div>
</div>
<button type="submit">Find Owner</button>
<a th:href="@{/owners/new}">Add Owner</a>
</form>HtmlFlow + DataStar (server-driven)
h2 { text("Find Owners") }
val lastName = dataSignal("lastName")
dataText { +" Searching for users with last name $lastName " }
input {
dataBind(lastName)
dataOn(Input) {
get(::searchhOwners)
modifiers { debounce(200.milliseconds) }
}
}
table {
tableHead()
tbody { tableBody() }
}
a { attrHref("/owners/new"); text("Add Owner") }- Server-driven UIs keep view code on the backend, reducing the need for a separate frontend codebase while still delivering interactive behavior.
- Fine-grained patches (signals/events) reduce network traffic compared with full page reloads.
- Kotlin + HtmlFlow provide compile-time guarantees for view changes and make refactors safer.