Real-time chat with Phoenix Channels, where each room is a supervised QuickBEAM runtime.
Browser A ──┐ ┌── Browser B
(WebSocket) │ │ (WebSocket)
▼ ▼
┌──────────────────────────────────────┐
│ Phoenix.Endpoint │
│ Socket → Channel ("room:general") │
└──────────────┬───────────────────────┘
│
Phoenix.PubSub ("room:general")
│
┌──────────────▼───────────────────────┐
│ DynamicSupervisor │
│ ┌──────────┐ ┌──────────┐ │
│ │ Room │ │ Room │ ... │
│ │ "general" │ │ "random" │ │
│ │ (QuickBEAM│ │ (QuickBEAM│ │
│ │ runtime) │ │ runtime) │ │
│ └─────┬─────┘ └──────────┘ │
└────────┼─────────────────────────────┘
│
┌────────▼─────────────────────────────┐
│ QuickJS context │
│ state.messages = [...] │
│ BroadcastChannel("chat") │
│ │ │
│ └→ Beam.callSync("broadcast", msg)│
│ → Phoenix.PubSub.broadcast! │
└──────────────────────────────────────┘
- Room per runtime — each chat room is a QuickBEAM runtime registered via
Registry, supervised byDynamicSupervisor. Created on first message (lazy). - JS manages state — message history lives in the JS context as a plain array. No external database.
- BroadcastChannel → PubSub — when JS calls
channel.postMessage(msg), theBroadcastChannel(backed by:pg) triggers a handler that callsPhoenix.PubSub.broadcast!. All subscribed Phoenix Channels push the message to their WebSocket clients. - Phoenix Channels — clients connect via WebSocket, join
room:<id>, receive history on join, and get live updates via PubSub. - Crash recovery — kill a room's process and it's cleaned up (transient restart). Next message to that room starts a fresh runtime.
mix deps.get
mix run run.exsOpen http://localhost:4000 in multiple browser tabs.
mix test- Actor-per-entity — one QuickBEAM runtime per room via Registry + DynamicSupervisor
- BroadcastChannel → Phoenix.PubSub — JS broadcasts become real-time WebSocket pushes
- Phoenix Channels — standard Phoenix real-time integration
- Lazy creation — rooms start on first use, no pre-provisioning
- OTP supervision — rooms are supervised, crash recovery is automatic
- Zero database — state lives in JS memory (add persistence via
Beam.callhandlers if needed)