A minimal MQTT 3.1.1 broker written in Elixir, built as the running example for the HappiHacking BEAM for Developers workshop.
Every BEAM concept in the workshop — processes, messages, memory, scheduling, supervision, observability, patterns — maps onto a concrete component in this broker. The code is deliberately small and readable so students can trace a packet from TCP socket to subscriber and back.
- Full MQTT 3.1.1 broker (CONNECT, PUBLISH, SUBSCRIBE, UNSUBSCRIBE, PING, DISCONNECT, will messages)
- QoS 0 and QoS 1 (QoS 2 is a workshop extension exercise)
- Retained messages, clean-session persistence, offline queues
- Authentication as pure-function gatekeeper
- Topic wildcards (
+,#) including the$-system-topic privacy rule
Application (supervision tree)
├── Registry tracks Sessions by client_id
├── RetainedStore last message per topic (GenServer + ETS)
├── TopicRouter subscription registry + fan-out
├── SessionSupervisor DynamicSupervisor
├── ConnectionSupervisor DynamicSupervisor
└── Listener TCP accept loop (Task)
Per client:
Connection (GenServer) ←TCP→ MQTT client
↕ Elixir messages
Session (GenServer) — subscriptions, offline queue, will
↕ Elixir messages
TopicRouter → broadcasts to matching subscribers
Walkthroughs and anti-pattern review live in docs/.
mix deps.get
iex -S mixBroker listens on port 1883 by default. Connect with any MQTT client:
mosquitto_sub -h localhost -t "sensors/#" -v
mosquitto_pub -h localhost -t "sensors/temp" -m "22.5"Configure port or auth in config/config.exs:
config :gnomeqtt,
port: 1883,
auth: [{"user", "pass"}] # or nil for no authmix testFive planted issues live on their own branches. Each has an EXERCISE.md
explaining the problem and how to observe it:
exercise/unbounded-queue— Session offline queue has no capexercise/binary-leak— TopicRouter accumulates ProcBin referencesexercise/bottleneck— every publish serializes through one TopicRouterexercise/missing-supervision— Connections and Sessions run unsupervisedexercise/god-process— Connection and Session merged into one process
Check one out and compare with main to see the fix:
git checkout exercise/unbounded-queue
cat EXERCISE.md
# ... investigate, propose a fix ...
git diff main -- lib/gnomeqtt/session.exReference implementation for teaching. Not production-ready — there is no QoS 2, no TLS, no persistence across broker restarts, and the TopicRouter is intentionally a single-process bottleneck.
MIT. See LICENSE.