Durable message broker in a single Go binary. Produce, consume, ack over plain HTTP — no SDK, no ZooKeeper. Queues, fan-out, delayed delivery, replay, and one-call replication. Proven with 300M+ soaked messages, zero lost. Apache-2.0.
No reviews yetBe the first to leave a review for Narad
Maker
📌
Hi PH 👋 I'm a backend engineer who spent the last few months building Narad to learn distributed systems properly — and it grew into something I'd actually run. It's a durable message broker in one Go binary: you produce, consume, and ack over plain HTTP with curl, and deploying is a single Helm chart — the cluster metadata (Raft) lives inside the same binary, so there's no ZooKeeper or external anything.
It's an AP system — no ordering guarantee, and the docs say so in paragraph two, not a footnote. Before calling it 1.0, I soaked 300M+ messages through a live cluster at 1,000/s with zero loss, ran a kill -9 chaos matrix, and benched 50k msg/s through the full flow. Every claim in the docs is verified against the code.
My favorite part shipped a day after 1.0: replication as a pattern, not a subsystem. One API call creates a live copy of a topic, deliberately placed on different machines. No quorums, no new machinery.
It's free and Apache-2.0. I'd love your questions. The launch post has the stories: https://debanganthakuria.vercel....
Report
the no-SDK HTTP approach is super refreshing, especially for polyglot setups. one thing that would help us adopt it: a built-in dead letter queue with configurable retry policies, so failed messages don't have to be reimplemented on every consumer side.
Report
Maker
@englhsrevz8oe Şengül — great question, and it's one I decided on purpose rather than by omission
Narad deliberately has no built-in retry engine, because retry policy is business logic: max attempts, backoff shape, which errors are retryable, where dead messages go — every team answers those differently, and a broker that hardcodes the answers becomes the thing you fight. What Narad gives you instead are three durable primitives — visibility leases, acks, and topics — that compose into every retry policy I know of, consumer-side, where you can see it and change it without a broker upgrade.
The full menu is documented here, and it's shorter than you'd expect: https://debanganthakuria.github.io/narad/client/handling-retries/ — a DLQ is literally just a topic you create (with the whole API available on it: alert on its depth, replay it, re-drive it after fixing the bug), bounded retries are a delivery_count field in your message envelope, and spaced backoff uses Narad's native delayed topics. The core pattern is ~10 lines of consumer code.
Your "reimplemented on every consumer side" concern is real though, and the honest answer to that isn't a broker feature — it's client SDKs. You can build one internally for your company that follows your specific retry methods.
If you try the patterns page and find a policy it can't express, tell me — that would genuinely change my mind about what belongs in the broker.
Report
A HTTP-only broker without SDK lock-in is genuinely useful, nice work. One thing that would help a lot for production rollouts: a built-in dead-letter queue option when messages hit max retries or get rejected on consume, so failures don't just sit there silently. Pair it with a simple GET to peek at the DLQ contents and teams can debug poison messages without digging through logs.
Report
Maker
@naz1683784 Naz — thank you! Both halves of this exist today, they're just composed from primitives instead of baked in as broker config.
DLQ: a dead-letter queue in Narad is a topic you create — jobs-dlq — and the docs ship the exact redrive recipe: carry delivery_count in your message envelope, and at max attempts produce to the DLQ and ack (in that order — crash-safe). Full pattern menu here: https://debanganthakuria.github.io/narad/client/handling-retries/ — it's ~10 lines of consumer code, and the policy (max attempts, backoff, per-error-class DLQs) stays yours instead of frozen into broker config. Why not built-in? Retry policy is business logic; every team answers it differently, and a broker that hardcodes the answers becomes the thing you fight.
"So failures don't sit there silently" — they won't: the DLQ is a normal topic, so its depth shows up in the Prometheus metrics every node serves (/metrics, zero config) and on the Grafana dashboard that ships in the repo. One alert rule on DLQ depth > 0 and you're paged before your users are.
The peek is my favorite part of your comment, because it's a first-class feature already: GET /consume?partition=P&offset=N is replay mode — a read-only point read of any retained message. No reservation, no receipt handle, no queue state disturbed; read the same poison message ten times while you debug it. GET /topics/jobs-dlq gives you per-partition offset ranges so you know where to point it. We hammered this path with thousands of reads against a live cluster under load before 1.0 — zero impact on consumers.
And once the bug is fixed: replay the DLQ, re-produce to the main topic, done. Same HTTP API end to end — which is kind of the whole thesis
Report
Tried it over the weekend and the single binary setup is genuinely refreshing, no Zookeeper dance, just one command and it's running. Really impressed by how the replay feature works over plain HTTP without needing an SDK.
the no-SDK HTTP approach is super refreshing, especially for polyglot setups. one thing that would help us adopt it: a built-in dead letter queue with configurable retry policies, so failed messages don't have to be reimplemented on every consumer side.
@englhsrevz8oe Şengül — great question, and it's one I decided on purpose rather than by omission
Narad deliberately has no built-in retry engine, because retry policy is business logic: max attempts, backoff shape, which errors are retryable, where dead messages go — every team answers those differently, and a broker that hardcodes the answers becomes the thing you fight. What Narad gives you instead are three durable primitives — visibility leases, acks, and topics — that compose into every retry policy I know of, consumer-side, where you can see it and change it without a broker upgrade.
The full menu is documented here, and it's shorter than you'd expect: https://debanganthakuria.github.io/narad/client/handling-retries/ — a DLQ is literally just a topic you create (with the whole API available on it: alert on its depth, replay it, re-drive it after fixing the bug), bounded retries are a delivery_count field in your message envelope, and spaced backoff uses Narad's native delayed topics. The core pattern is ~10 lines of consumer code.
Your "reimplemented on every consumer side" concern is real though, and the honest answer to that isn't a broker feature — it's client SDKs. You can build one internally for your company that follows your specific retry methods.
If you try the patterns page and find a policy it can't express, tell me — that would genuinely change my mind about what belongs in the broker.
A HTTP-only broker without SDK lock-in is genuinely useful, nice work. One thing that would help a lot for production rollouts: a built-in dead-letter queue option when messages hit max retries or get rejected on consume, so failures don't just sit there silently. Pair it with a simple GET to peek at the DLQ contents and teams can debug poison messages without digging through logs.
@naz1683784 Naz — thank you! Both halves of this exist today, they're just composed from primitives instead of baked in as broker config.
DLQ: a dead-letter queue in Narad is a topic you create — jobs-dlq — and the docs ship the exact redrive recipe: carry delivery_count in your message envelope, and at max attempts produce to the DLQ and ack (in that order — crash-safe). Full pattern menu here: https://debanganthakuria.github.io/narad/client/handling-retries/ — it's ~10 lines of consumer code, and the policy (max attempts, backoff, per-error-class DLQs) stays yours instead of frozen into broker config. Why not built-in? Retry policy is business logic; every team answers it differently, and a broker that hardcodes the answers becomes the thing you fight.
"So failures don't sit there silently" — they won't: the DLQ is a normal topic, so its depth shows up in the Prometheus metrics every node serves (/metrics, zero config) and on the Grafana dashboard that ships in the repo. One alert rule on DLQ depth > 0 and you're paged before your users are.
The peek is my favorite part of your comment, because it's a first-class feature already: GET /consume?partition=P&offset=N is replay mode — a read-only point read of any retained message. No reservation, no receipt handle, no queue state disturbed; read the same poison message ten times while you debug it. GET /topics/jobs-dlq gives you per-partition offset ranges so you know where to point it. We hammered this path with thousands of reads against a live cluster under load before 1.0 — zero impact on consumers.
And once the bug is fixed: replay the DLQ, re-produce to the main topic, done. Same HTTP API end to end — which is kind of the whole thesis
Tried it over the weekend and the single binary setup is genuinely refreshing, no Zookeeper dance, just one command and it's running. Really impressed by how the replay feature works over plain HTTP without needing an SDK.