Skip to content

Build a live poll

This tutorial teaches beryl by building one app: a live poll. If you want a shorter path, start with the Quick Start. All prose and code excerpts come from the runnable examples/live_poll/ project. beryl is pre-1.0. Expect the API to change.

  1. The Elm architecture, without a DOM
  2. One update function, many socket events
  3. Typed messages from the rest of your Gleam system
  4. Composition: raw dispatch and beryl/channel
  5. Where the analogy ends
  6. Supervising beryl

You can read each chapter on its own. The checkpoints build on each other. They start with a read-only poll and end with a supervised channel system.

Run each command from the repository root:

ChapterCommandURL
1cd examples/live_poll && gleam run -m live_poll/step_01http://localhost:8101
2cd examples/live_poll && gleam run -m live_poll/step_02http://localhost:8102
3cd examples/live_poll && gleam run -m live_poll/step_03http://localhost:8103
4cd examples/live_poll && gleam run -m live_poll/step_04http://localhost:8104
5cd examples/live_poll && gleam run -m live_poll/step_05http://localhost:8105
6cd examples/live_poll && gleam run -m live_poll/step_05http://localhost:8105

Stop a checkpoint with Ctrl-C before you start the next one. The browser client loads Phoenix JavaScript 1.7.20 from unpkg, so the first page load needs internet access. The Gleam server and the beryl runtime run on your machine.

The five step_0N.gleam files are short entry modules. Each one picks a configuration and starts the shared modules:

  • raw.gleam has the raw init, Model, Message, and update function used by steps 1 through 3.
  • channel_handler.gleam has the two channel.Handler values used by steps 4 and 5.
  • poll.gleam defines the poll types and turns client Dynamic payloads into poll commands.
  • store.gleam keeps poll state in a Gleam OTP actor.
  • timer.gleam runs delayed callbacks from its own actor.
  • server.gleam starts the beryl child specification and the Mist transport, serves the browser client, and can serve /healthz.
  • test/live_poll_test.gleam tests the poll types and the protocol decoder.

Read each step module together with the shared files. A step file shows which behavior is on. It does not repeat the runtime, poll, or browser code.

The tutorial uses these terms, and keeps them separate:

  • A socket is one client connection that the beryl runtime knows about.
  • A topic is a string that a socket subscribes to, such as poll:demo.
  • Raw dispatch is the core API. Your app defines a Model and an update function. The update function receives a socket.Input, and returns a socket.Next with a list of socket.Effect values. A socket.Sender lets other code send typed messages to one socket.
  • A channel is one accepted topic on one socket, managed by beryl/channel. A handler matches a topic pattern and builds that channel: its private state, its callbacks, and its typed info messages.
  • A channel callback returns a list of actions. Each action applies to the callback's own topic.
  • The runtime has one router actor and one actor for each connected socket. The socket actor holds the model and runs effects. The router keeps the socket index and sends broadcasts to subscribers.
  • A Gleam OTP process.Subject is a typed address for a process mailbox. A beryl socket.Sender is narrower. It delivers typed Info only to the socket that owns it. After the socket disconnects, the runtime drops the message.

Do not use Subject, Sender, socket, topic, channel, or handler in place of one another. Each term names a different part of the system.

Raw dispatch is beryl's core, and the clearest way to learn it. For apps with many channels, or apps shaped like Phoenix, use beryl/channel.