Skip to content

Examples

Four runnable applications in the examples/ directory use a Gleam/BEAM backend and a browser frontend. The repository also contains the live-poll tutorial, load-test server, and shared example helpers.

The installed package does not include the examples. Clone the repository:

Terminal window
git clone https://github.com/tylerbutler/beryl.git
cd beryl

Source: examples/showcase

This example uses one socket runtime and three topic families. The beryl/channel API combines cursor, chat room, and shared document handlers in one supervised socket system. It does not need a socket-wide model or a custom router.

Terminal window
cd examples/showcase
gleam run
# Open http://localhost:8000
beryl featureHow it's used
Channel compositionOne channel.child_spec handler table owns cursor:*, room:*, and the document: prefix
Per-channel stateEach joined topic keeps its own private state; the layer prunes it on close
Join and close actionsJoin and termination callbacks return actions that beryl runs in list order
Per-pattern rate limitswith_topic_rate gives cursor traffic a higher limit than chat and document traffic
Shared presenceOne ETS-backed session-presence tracker serves cursors and chat, publishing snapshots asynchronously
Single WebSocket endpointEvery embedded app shares /socket/websocket
Browser (Phoenix JS clients across /cursors, /chat, /docs)
│ WebSocket (Phoenix wire protocol)
Server (Gleam)
├── Mist HTTP — serves the landing page and each example UI
├── beryl/channel — handler table owning cursor:*, room:*, document:
├── per-channel state — private to each joined topic
├── session presence — shared across cursors and chat
└── groups + doc store — example-specific state

Source: examples/cursors

Move your mouse to send its position in real time. Open the app in multiple browser tabs to see other cursors.

Terminal window
cd examples/cursors
gleam run
# Open http://localhost:8000 in multiple browser tabs
beryl featureHow it's used
Raw dispatchOne socket-wide model handles the cursor:* topic family
Topic routingThe update function matches cursor:* directly with beryl/topic
Session presence (ETS)The example-local session_presence tracker stores connected users and their username + color metadata in ETS
broadcast_fromSends cursor moves to all other clients, excluding the sender
Rate limitingberyl.with_message_rate throttles high-frequency cursor events
WebSocket transportmist_transport.upgrade handles Phoenix-compatible WebSocket requests
Phoenix JS clientFrontend uses the official phoenix package over the standard wire protocol
Browser (vanilla JS + Phoenix JS client)
│ WebSocket (Phoenix wire protocol)
Server (Gleam)
├── Mist HTTP — serves HTML + static files
├── beryl app dispatch — cursor:* topics
├── example session_presence — ETS-backed user tracking
└── beryl pubsub — broadcast_from cursor positions

Source: examples/chatrooms

A multi-room chat app with authentication, join rejection, typing indicators, and message acknowledgment.

Terminal window
cd examples/chatrooms
gleam run
# Open http://localhost:8001?token=beryl-demo in multiple browser tabs

This example uses parts of the beryl API that the cursor example does not use:

beryl featureHow it's used
on_connect authToken query param validated before WebSocket upgrade is accepted
channel.rejectRooms reject joins when full (20-user cap) or when room doesn't exist
channel.reply_okDelivery of new_msg confirmed with an ok-status phx_reply
Coded error repliesEmpty messages rejected with HTTP-style code 422 in the reply payload
GroupsThree rooms (general, random, help) organised in a named group
Session presence (ETS)The example-local session_presence tracker stores online users and typing metadata in ETS
System messages"user joined" / "user left" broadcasts from join and termination actions
Multiple topicslobby and room:* handlers compose in one channel.child_spec table
Rate limitingwith_join_rate (5/sec) and with_channel_rate (10/sec/channel)
Browser (vanilla JS + Phoenix JS client, token auth)
│ WebSocket (Phoenix wire protocol, ?token=beryl-demo)
Server (Gleam)
├── Mist HTTP — static files, /api/rooms
├── Mist WebSocket transport — on_connect validates token
├── beryl/channel — lobby + room:* handlers
├── beryl groups — "public" group → general, random, help
└── example session_presence — ETS-backed online users + typing indicators
DirectionEventPurpose
Client → Servernew_msgSend a chat message {text}
Client → ServertypingStart typing indicator
Client → Serverstop_typingStop typing indicator
Server → Clientnew_msgBroadcast message {text, username, color, type, timestamp}
Server → Clientphx_reply (push ref)Reply to a client push — used for both delivery acknowledgment and validation errors. The demo returns validation failures with channel.reply_ok, so their wire status is still ok; the response payload ({code, error}) represents the domain validation error.
Server → Clientpresence_listUpdated online user list
Server → ClienttypingTyping indicator update

Source: examples/collab_docs

This shared document editor merges block state in each client with a CRDT. It uses beryl as an unordered real-time transport.

Terminal window
just deps
cd examples/collab_docs && gleam run
# Open http://localhost:8002 in multiple browser tabs
beryl featureHow it's used
Segment-shaped topicsThe document:* handler claims the prefix, then validates each document:<tenant>:<document> topic on join
Client-side CRDT mergeBrowser state uses lattice_core, lattice_maps, and lattice_registers with ORMap(MVRegister(String)) document blocks
Unordered realtime transportberyl broadcasts document updates while the CRDT handles merge convergence
Late joiner cacheServer returns cached merged state in the join reply for new clients
Conflict resolution UIConcurrent edits to the same block render explicit conflict cards with all versions
Browser (vanilla JS + Phoenix JS client + lattice CRDT packages)
│ WebSocket (Phoenix wire protocol)
Server (Gleam)
├── Mist HTTP — serves HTML + static files
├── beryl/channel — document:* handler with tenant/document validation
├── document cache — merged state for late joiners
└── beryl pubsub — sends CRDT updates to subscribers

Starting pointGo here
I want a minimal working example right nowQuick Start
I want to see live presence + cursorsexamples/cursors
I want auth, join validation, or groupsexamples/chatrooms
I want collaborative documents or CRDT conflictsexamples/collab_docs
I want to understand the dispatch APIChannels guide
I want to add presence to my appPresence guide