ipnon
Hello everyone,
Long time lurker, first time poster. I’ve learned a lot from this community already so let me begin by giving my thanks for this. My question is quite simple and high level: What is the basic architecture of a chat room using a LiveView? Why or why not should a Channel be used?
I’ve read the first two parts of Stephen Bussey’s “Real-Time Phoenix”, which greatly clarified the function and role of Socket and Channel in Phoenix, and their individual responsibilities. Having a chat application in mind, and finding the book quite motivating I decided to begin writing a prototype before finishing the book.
But I quickly became stuck due to some confusion about a few things. I have a LiveView of my Chat resource that has a name and room number (two users should be able to connect to the same example.com/chat/:room_number and chat), a User resource generated with mix.gen.auth, and a join table such that there is a many-many relationship between the two tables. According to the Phoenix docs and “Real-Time Phoenix” the best practice would be to create a Channel for the individual chat clients to connect to. But why should a Channel be created and have clients connect to it when there is already a stateful WebSocket connection to the server via the LiveView?
Should a new Channel be used in this circumstance, even though it would require adding some JS to the client? If not, then how can a subscription to the shared broadcast be maintained in the LiveView “controller” (I’m not sure if this is the right terminology)? I tried creating a new Channel, and subscribing to it within the LiveView, thus avoiding a new Socket connection.
This is a sketch of the approach I had in mind:
defmodule MyAppWeb.ChatLive.Show do
use MyAppWeb, :live_view
alias MyApp.Chats
@impl true
def mount(%{"room_number" => room_number}, _session, socket) do
if connected?(socket) do
# Is this the right approach or a foul hack?
# Assume there is a basic Channel that simply passes along all messages to all who have joined, like in the tutorial.
MyAppWeb.Endpoint.subscribe("chat:#{room_number}")
end
{:ok, assign(socket, :room_number, room_number, :messages, [])}
end
@impl true
def handle_event("send_message", %{"message" => message}, socket) do
MyAppWeb.Endpoint.broadcast("chat:#{socket.assigns.room_number}", "new_message", %{body: message})
{:noreply, socket}
end
@impl true
def handle_info(%{event: "new_message", payload: %{body: body}}, socket) do
{:noreply, update(socket, :messages, fn messages -> [body | messages] end)}
end
end
This seemed like a code smell, which is when I decided to come here for advice. I feel quite confident that with some sage guidance on high-level architecture I can avoid any major footguns and hack through the rest of the details.
Thanks,
Ipnon
Trending in Questions
Other Trending Topics
Categories:
Sub Categories:
Forums
Popular Tags
- #ecto
- #liveview
- #troubleshooting
- #learning-elixir
- #deployment
- #library
- #erlang
- #testing
- #genserver
- #mix
- #absinthe
- #remote-other
- #otp
- #plug
- #how-to-question
- #macros
- #postgres
- #channels
- #elixirconf
- #exunit
- #discussion
- #code-sync
- #javascript
- #podcasts
- #onsite
- #dialyzer
- #docker
- #authentication
- #umbrella
- #full-time-contract
- #podcasts-by-brainlid
- #ecto-query
- #elixir-ls
- #blog-post
- #phoenix_html
- #iex
- #graphql
- #ai
- #genstage
- #elixirconf-us
- #websockets
- #supervisor
- #advent-of-code
- #distillery
- #processes
- #api
- #forms
- #metaprogramming
- #security
- #hex










Showing Posts 1 to 3- Show Best Posts
- Show All Posts (oldest first)
- Show All Posts (newest first)
ipnon
I’ve come back to this after the weekend. It is still a work in progress, but it seems a simple PubSub process shared between all of the chat LiveView client processes is the correct approach. Once there is a working prototype I’ll create a simple demo.
LLMs have changed my workflow so much, I’ve realized, that basic tasks like RTFM have gathered some dust in my toolbox. There is another thread on LLM performance with Elixir, and in my recent experience (I first learned Elixir before the current AI boom and only recently returned to writing it full time) ChatGPT very confidently will lead you in the wrong direction regarding LiveView. It has struggled immensely with creating simple forms, imports, any sort of knowledge where best practices have changed immensely in the last few years, yet when implementing functions with well-defined inputs and outputs, as is so often the case with Elixir, it’s basically flawless. The experience really highlights how Phoenix is a paradigm shift from the current mode of Python/Ruby/OOP backend and React/Native/reactive frontend: LLMs don’t learn new paradigms well, and by their nature gravitate (literally descending on a gradient) towards the tried-and-true way of doing things. An interesting thought for those most bullish on AI …
ipnon
This seems to be a great example: GitHub - dwyl/phoenix-liveview-chat-example: 💬 Step-by-step tutorial creates a Chat App using Phoenix LiveView including Presence, Authentication and Style with Tailwind CSS · GitHub
My own solution turned out something like this:
I used the Phoenix generators to create a user resource in an accounts context, and then a chat and message resource in the chats context. Messages are associated with particular users and chats when they are created in order to display them in the right chat with the right sender. In terms of technical problems I spent a good few hours before realizing excluding
phx-update="stream"from the immediate parent of the stream invocation in the template results in undefined behavior. Perhaps a compiler warning would be helpful for new users here.My major conceptual misunderstanding was the simplicity of info passing in Elixir. My intuition told me it couldn’t really be as simple as 3 lines, and you have a real-time distributed messaging system coordinating between a server and many clients.
Phoenix has left a strong impression on me that it’s about as close as you can get to an ideal API for distributed systems. In the conventional apps I’ve been writing for many years you are ultimately trying to approach this same API in every project, but the differences in state between server and client, and the inevitable side-effect leaking allowed by OOP languages and JavaScript means you slowly diverge from this API until you have a big ball of mud.
The last week has been a learning process but I’m able to add features to my app very quickly now. I can tell this language Elixir is a labor of love, which is why although I had no replies to this thread I wanted to share what I learned with the community.
jam
Also ran into this
. Would be great to have a complier error if it sees a
@streamin the markup without an accompanyingphx-update="stream"