An MQTT 3.1.1 broker that validates and routes Protobuf messages at the network layer. Written in Zig. MIT licensed.
Standard MQTT brokers are payload-agnostic — they forward bytes without knowing or caring what is inside. ProtoMQ adds a schema layer on top: each topic is mapped to a Protobuf message type, and the broker validates every incoming PUBLISH against that schema before routing it to subscribers.
Invalid payloads are dropped at the broker, so consumers never have to write defensive parsing code.
Schemas are plain .proto files — no code generation, no build step.
protoc required+, #)$SYS/discovery/requestRequires Zig 0.15.2 or later. Download from ziglang.org.
# Build
zig build -Doptimize=ReleaseSafe
# Run
./zig-out/bin/protomq-server
# CLI — publish with automatic JSON → Protobuf encoding
./zig-out/bin/protomq-cli publish -t sensor/data \
--json '{"device_id":"pi5","temperature":21.3,"humidity":55.0}'
# CLI — discover schemas from the running broker
./zig-out/bin/protomq-cli discover --proto-dir schemas
Drop .proto files into the schemas/ directory. They are parsed on startup.
Map a topic to a message type in src/server/tcp.zig:
// src/server/tcp.zig — TcpServer.init()
try server.schema_manager.loadSchemasFromDir("schemas");
try server.schema_manager.mapTopicToSchema("sensor/data", "SensorData");
Then rebuild and restart. Or do it at runtime — see the Admin API section below.
Build with -Dadmin_server=true to enable an HTTP server on 127.0.0.1:8080.
When the flag is off, the HTTP code is entirely absent from the binary — not just disabled.
zig build -Doptimize=ReleaseSafe -Dadmin_server=true
All endpoints require Authorization: Bearer <ADMIN_TOKEN>
(env var, defaults to admin_secret).
| Method | Path | Description |
|---|---|---|
GET |
/metrics |
Active connections, messages routed, loaded schema count |
GET |
/api/v1/schemas |
Current topic → message-type mappings as JSON |
POST |
/api/v1/schemas |
Register a new schema and mapping at runtime, no restart needed |
# Register a schema at runtime
curl -X POST http://127.0.0.1:8080/api/v1/schemas \
-H "Authorization: Bearer admin_secret" \
-H "Content-Type: application/json" \
-d '{
"topic": "telemetry/gps",
"message_type": "GpsCoord",
"proto_file_content": "syntax = \"proto3\";\nmessage GpsCoord { float lat = 1; float lon = 2; }"
}'
# Check metrics
curl -s -H "Authorization: Bearer admin_secret" http://127.0.0.1:8080/metrics
# {"connections":3,"messages_routed":142,"schemas":2,"memory_mb":0}
Benchmarked on two machines. Numbers are from the full benchmark suite in benchmarks/.
| Scenario | Apple M2 Pro | Raspberry Pi 5 |
|---|---|---|
| p99 latency · 100 clients | 0.44 ms | 0.13 ms |
| Concurrent connections | 10,000 | 10,000 |
| Sustained throughput | 9,000 msg/s | 9,000 msg/s |
| Peak throughput (small msgs) | 208,000 msg/s | 147,000 msg/s |
| Memory · 100 clients | 2.6 MB | 2.5 MB |
| Memory leaks · 100k cycles | 0 | 0 |
Memory baseline increases from ~2.6 MB to ~4.0 MB when the Admin Server is compiled in. The Admin Server shares the same event loop as the MQTT broker — it does not run on a separate thread.