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:clickto save the time waiting forpointerup/mouseup.
- Transform the existing DOM into the new modified one, while preserving the state.
- Here's an article written by Jorge Manrubia, demonstrating the difference between
<body>swapping and morphing.
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.