Offline-enabled apps with Phoenix LiveView and LiveSvelte

Hi everyone! I’m new to the forums and new to Elixir in general. To learn Elixir, I recently just completed my first app. It’s a todo app built with Phoenix LiveView, but with a twist, it can also work offline :smirk:

By using the LiveSvelte package, I was able to use LiveView to render Svelte components which allows for offline-support. I know an offline app was not what LiveView was intended for, but I thought it was neat that it was possible to still use LiveView in such an app.

I haven’t gotten the chance to dive deeper into Phoenix Channels yet so I’m not sure if the app would perform better just using that instead. However, so far the app has been working well for me and the DX of using LiveView has been great. Anyhow, I thought I would share in case anyone else is interested in building offline-enabled apps with LiveView :slight_smile:

Here’s a video I made walking through how I made the app:

Source code:

Live Demo:

Any questions and feedback welcomed!

49 Likes

Very cool. I tried it out and it works as promised.

Would you mind putting a license on the repo so that I know what I’m allowed to do with the code?

7 Likes

Thanks for checking out the app!

And yes, I just added an MIT license. Please feel free to use the code as you please :slight_smile:

10 Likes

Very cool to see a CRDT example with Elixir. This was on my bucket list of things to try out. Thanks for sharing.

4 Likes

Awesome work. Would you mind adding a .env.example for us newbies?

1 Like

Absolutely, I just added a .env.example to the repo:

I included some comments in the file for more explanation, but in short, it holds the two variables:

  • JS_BACKEND_URL - The server I’m using to 1. run Yjs server-side, and 2. send emails via Cloudflare Workers.
  • JWT_PRIVATE_KEY - Private key used to sign JWTs needed when sending requests to the JS backend server.

Please see the comments in the file for more info and feel free to let me know if you have any questions!

3 Likes

Cheers! Will try to get the Yjs backend setup myself, hopefully no issues.

2 Likes

Very good explanation of the offline enabled app and it’s concepts.

2 Likes

Hi all! I wanted to give an update on this project-

In my video, I discussed using Yjs on the server-side in order to merge the states of the various clients. This worked, but was not ideal since it required running JS from Phoenix or from a separate JS backend server (which is what I did for my project via a Cloudflare Worker).

Since then, I have found out that it is not necessary at all to run Yjs on the server in order to sync the clients. I have updated my repo to no longer need a separate JS backend. I also made a video explaining how it is possible to do so. You can check it out here if interested:

4 Likes

Thanks! That’s quite interesting, but it is clearly problematic for any real-life cases with non-trivial amount of data. I think our best bet as community would be to create a Rustler wrapper package around yrs - Rust to be able to reconcile state diffs (patches). That way we could use it for realistic data amounts and keep the chattiness of the network traffic at bay.

But yeah, someone with Rust skills would have to create the wrapper first :slight_smile:

Maybe we’ll have a production quality CRDT bindings in near future.
Cheers!

3 Likes

I definitely agree that sending the entire app state every time is not scalable and a Rustler wrapper would be awesome!

One other idea I had that may also work is: Instead of sending the entire app state every time, we could just send Yjs updates for each change. And, on the server, instead of keeping a single app state, we can keep a table of all updates sent. Then, when a client reconnects, it can request all the updates it has missed. I haven’t tried this out, but I think that it should work in theory.

Definitely still not as good as just having a Yjs port, but just thinking out loud :slight_smile:

Indeed, using the diff on the client to send just the changed data might work. The only thing I dont like, it the in-ability on the server to process the data in any meaningful way. For your Elixir app this data is just a binary blob without much meaning. It really depends on the use case, for some apps this might be OK. My use case requires some insight into the data on the server, so it wont work out here. BUT… Still great, in case the server should stay dump and clients just exchange data.

Lots of good ideas floating around! :wink:

1 Like

Yup, it definitely depends on the use case. For the apps I had in mind, I was actually considering encrypting the user data so that it would only be available to the end user on the client-side. In those cases, not being able to process data server-side actually works out.

Thanks for all the feedback! I’ll definitely have to ponder this one a little more. :smirk:

This came across my radar today - https://extism.org/

It allows you to compile JS to WASM which can then be ran in Elixir! I haven’t played with it at all, but perhaps this can be another way to run Yjs on a Phoenix server.

2 Likes

I spoke about the app a little more on the Elixir Mentor Podcast. Here’s a link to the recording if anyone is interested!

4 Likes

FYI: ywasm is a WASM port of Yjs.

How will you run Yjs in your Elixir server? Just start it up when the server starts? Or create a GenServer that calls on it when there’s a connection request?

1 Like

Hi Rico, thanks for sharing the link!

Since I haven’t tried it out yet, I can’t give a firm answer. My initial thought is to first keep things simple- I’d would just make the call to run Yjs via WASM on each request, without a GenServer, to see if that works. Then, I’d try to improve from there once I have a better idea of how things work.

Just a general comment.

Automerge has worked well for me in an Phoenix LiveView web app.

In case someone is interested in an alternative to yjs.

5 Likes

Automerge is a really cool CRDT solution as well! I haven’t tried in much besides setting up a hello world project with it once.

I’m curious, how did you handle syncing? Were you using Automerge’s public sync server or did you build your own Phoenix solution?

I did the latter.

I used a Rust NIF to be able to create and merge crdt docs on the Elixir server. In combination with creating, merging and editing on the client side with their js library.

3 Likes