Getting Started with Datastar - Build a Rust + Axum Todo App - HAMY
Blog<br>Projects<br>Notes<br>About<br>49
Getting Started with Datastar - Build a Rust + Axum Todo App<br>Essay - Published: 2026.04.08 | 8 min read (2,048 words)<br>build | create | datastar | rust<br>DISCLOSURE: If you buy through affiliate links, I may earn a small commission. (disclosures)
For the past several years I've been building server-side rendered apps using hypermedia libraries like HTMX and Datastar to sprinkle in interactivity where it's useful. I like this approach because it's simple, efficient, and allows me to use whatever language / stack I want - everything can write strings and serve web requests and therefore can serve hypermedia.
I've also been learning and using High-Level Rust so it was only natural we combine the two at some point.
This post continues my fullstack Rust web series:
Build a Simple Single-File Rust Web API
Build a Single-File Rust Web API with SQLite
Build a Fullstack SSR Web App with Rust + Maud
Here we'll sprinkle in interactivity with Datastar.
What we're building
We're building a simple CRUD todo list app:
Create todos
Read a list of todos
Update the completed status of todos
Delete todos
Tech stack:
Backend : Rust + Axum
Data : Sqlite and sqlx
Frontend : SSR HTML with Maud and Datastar for interactivity (using the datastar crate)
What is Datastar
Datastar is a newer entry to the Hypermedia space and is the most unusual choice in this stack so wanted to take a minute to explain a bit about what it is and how it works.
The core idea behind hypermedia frameworks is:
The backend controls the frontend - little to no client-side logic
You can partially rerender pages with HTML fragments - improving reactivity and composability of SSR HTML when compared to MPAs
This reduces code complexity and can improve performance by reducing layers of transformation logic and duplication across client/server
Datastar takes this a little further than others like HTMX by:
SSE first - HTML fragments and signals all pass over same SSE connection
Built in signals - so can do both client and server interactivity, similar to an Alpine + HTMX setup
In this example we're using 3 key server to client primitives:
PatchElements - Server sends HTML fragments, Datastar places them in the DOM via CSS selectors
PatchSignals - Server sends JSON, Datastar updates the reactive client-side signals (this is where Alpine is often used)
ExecuteScript - Server tells the browser to run some javascript - for eg alerts or navigation
How it's built
We'll be focused mainly on the view layer and Datastar in this post to keep this guide streamlined and understandable. To see more details on the other layers, checkout the other posts in this series.
If you want the full source code of this project so you can clone it and run it yourself, checkout the HAMY LABS Code Example repo on GitHub. This is available to HAMINIONS Members.
Models & Errors
The Models and Errors code is largely the same as it was in the previous installment of the series:
Todo - Domain representation of a Todo
CreateTodo - Dto for deserializing Create request payloads
TodoRow - Data representation of a Todo
#[derive(Clone, LightClone)]<br>struct Todo {<br>id: Uuid,<br>title: Arc,<br>completed: bool,
#[derive(Deserialize)]<br>struct CreateTodo {<br>title: String,
#[derive(FromRow)]<br>struct TodoRow {<br>id: Uuid,<br>title: String,<br>completed: bool,
The errors are also the same:
enum TodoError {<br>NotFound(Uuid),<br>Internal(sqlx::Error),
Service Layer
The Service Layer is similarly untouched:
TodoService trait - interface with async list, get, create, complete, and delete methods
SqliteTodoService - implementation that calls into sqlite
#[async_trait]<br>trait TodoService: Send + Sync {<br>async fn list(&self) -> Result, TodoError>;<br>async fn get(&self, id: Uuid) -> Result;<br>async fn create(&self, input: CreateTodo) -> Result;<br>async fn complete(&self, id: Uuid) -> Result;<br>async fn delete(&self, id: Uuid) -> Result;
Views - SSR HTML with Datastar Attributes via Maud
This is where we start to see Datastar. HTML templates include data-* attributes that Datastar interprets. If you're coming from HTMX this is similar to the hx-* attributes.
The views are split into page-based components. These are composed together to build the full page but each can be rendered independently which powers the individual component endpoints which is how we get partial rerenders.
Typically how this works is:
Use coarse-grained components - given a domain object, how does it render on this page?
Main endpoint for the page - renders the full DOM tree
View endpoints under that page - render just the requested item
This is similar to the Backend for Frontend API design approach, but we return HTML fragments and signals instead of just JSON.
Layout - The base layout of our page, note that it pulls in Datastar. Gotcha: Datastar's public API has been changing a lot as it goes through 1.0 RC candidates so you...