The Datastar Way
The ideal (not dogmatic) way of using Datastar to build real-time, collaborative web apps, as well as simple websites using this mental model, without losing any performance.
Additional Infos
- Credit me (Huangphoux) if you use any part of this.
- All linked sources are of my own picking. I don't get paid to write any of this.
- If you're productive with React or htmx, keep using it.
- Architectural decisions in a team is sociological.
- Think twice before deciding on React.
- The shift in thinking from htmx to Datastar is as big as from React to htmx.
- There are other hypermedia libraries, as well as a comparision between some of them.
-
Hypermedia System
- Respond with HTML instead of JSON.
- Use
data-onto generalize hypermedia controls:data-on:click="@get('/endpoint')"- Any element can make HTTP requests:
data-on - Any event can trigger HTTP requests:
click - Use Backend Actions to send any type of requests:
@get('/endpoint')
- Any element can make HTTP requests:
-
Fat Morphing
- Respond with the entire modified page.
- The morphing algorithm will convert the old page into the new modified one.
-
SSE: open an long-lived connection to stream reponses to the client.
-
Brotli: compress the whole stream with tunable memory.
Without much adjustments (differentiating users by session IDs), by doing this way, multiplayer is the default behavior.
data-on: the only attribute that you would need
- There are other
data-onattributes for non-standard events as well:
Additional Infos
- The string evaluated by
data-onattributes are Datastar expressions, you can do more than using only Backend Actions.data-on:click="confirm('Are you sure?') && @delete('/examples/delete_row/0')"
- Be sure to use request indicators, as they are an important UX aspect of any distributed apps:
data-indicator:_fetching,data-attr:disabled="$_fetching"$_fetchingis a signal
- Suitable for collaborative app: all users see the same updated page
- Behave similarly to htmx's
hx-boost, but Datastar morphs and retains elements, instead of swapping the whole<body>like htmx. - Yes, you're doing MPA (Multi-Page Application). It's hypermedia-driven. Did you read the book?
- Send the entire modified page instead of small, specific fragments.
- Reduce the amount of endpoints needed to handle fragment updates.
Additional Infos
- Be sure to use Event Bubbling
- Use
data-on:pointerdown/mousedownrather thandata-on:click→ No need to wait forpointerup/mouseup
You don't actually need to know how the algorithm works under the hood if you respond with the whole page anyways.
Additional Infos
This is one way of doing Fat Morphing, Datastar can do polling just fine.
Optimal Fat Morphing
- To do Fat Morphing optimally, we should limit the number of endpoints that can change the view, since we don't need to send small fragments.
- CQRS stands for Command Query Responsibility Segregation, meaning separating the Commands and the Queries of data apart.
- Commands:
Create,UpdateandDelete- HTTP equivalence:
POST,PUT,PATCH,DELETE - These are all actions that change the data. These should not directly update the view.
- Queries already take care of that responsibility.
- Instead of patching elements, they can patch signals.
- HTTP equivalence:
- Queries:
Read- HTTP equivalence:
GET - This action can retrieve data and watch for data changes to compute the view
- Work Sharing, or Caching: make sure the query runs only once if multiple users are requesting the same view.
- HTTP equivalence:
- Queries watch for data changes by watching for new Command.
- Whenever there's a new Command, the Queries retrieve the modified data.
- Use the Publish-Subscribe pattern to implement Event-Driven Architecture
- By using CQRS, our app becomes the purest
view=function(state) - A function turning state into view: present data using HTML, then compute it as a page ⇒ Data drives views
- All data stored and processed on the back end
- On every data change, the page gets re-computed
- Each page only needs one single function to compute the page
Suitable for real-time apps: updates can be sent in a stream that get compressed for its whole duration
- Use HTTP/2 or HTTP/3 to allow for more connections
text/htmlfor the initial page load, thentext/event-streamfor subsequent responses
- A lossless data compression algorithm.
- Specifically created to compress HTTP stream.
- Compressing a stream of data is better.
- There would be a lot of duplications in the stream.
- Compression reduces those duplications effectively by forward and backward referencing.
- Compression ratio is much larger over streams than when compressing a single HTML/JSON response.
- Tunable context window
- The memory shared between the server and the client, stores the past and future data.
- You should increase it from the default 32 kB to reduce network and CPU usage on the client.
- A demonstration of Brotli's effective compression by Anders Murphy.

Stats and comparison for nerds
- Here are some anecdotal statistics, provided by members of the Datastar Discord server.
- Screenshot by winkler1.
- Compressed 26 MB down to 190 kB, over 1.2 minutes.
- Compression reduced size by 137 times.
- Testimony provided by jeffmarco
- Compressed 6 MB down to 14 kB, over 30 seconds.
- Compression reduced size by 429 times.
- Screenshot by winkler1.
- Gzip can't look ahead effectively and can't look back at all.
- Non-adjustable 32 kB context window.
- Not built with streaming support in mind.
- Worse than Brotli 4 to 6 times over a stream.
- Using gzip for streaming is still better than regular request-response.
- A comparison between gzip and Brotli, done by Anders Murphy.
- Over the same size SSE streams, out of the box, Brotli compresses 2 times better than gzip.
- With tunable memory, you can get 3 times (or more!) better compression.
Here are some notes regarding using Datastar that aren't important to be included in this document.