惯性聚合 高效追踪和阅读你感兴趣的博客、新闻、科技资讯
阅读原文 在惯性聚合中打开

推荐订阅源

Martin Fowler
Martin Fowler
大猫的无限游戏
大猫的无限游戏
J
Java Code Geeks
罗磊的独立博客
雷峰网
雷峰网
G
Google Developers Blog
Cyber Security Advisories - MS-ISAC
Cyber Security Advisories - MS-ISAC
爱范儿
爱范儿
B
Blog RSS Feed
腾讯CDC
Apple Machine Learning Research
Apple Machine Learning Research
D
Docker
Recent Announcements
Recent Announcements
T
Tailwind CSS Blog
博客园 - 聂微东
让小产品的独立变现更简单 - ezindie.com
让小产品的独立变现更简单 - ezindie.com
Vercel News
Vercel News
小众软件
小众软件
人人都是产品经理
人人都是产品经理
云风的 BLOG
云风的 BLOG
IT之家
IT之家
Blog — PlanetScale
Blog — PlanetScale
I
InfoQ
S
SegmentFault 最新的问题

DEV Community

Authentication Security Deep Dive: From Brute Force to Salted Hashing (With Java Examples) Why AI Systems Don’t Fail — They Drift Spilling beans for how i learn for exam😁"Reinforcement Learning Cheat Sheet" I Replaced Chrome with Safari for AI Browser Automation. Here's What Broke (and What Finally Worked) How Python Borrows Other People's Work The $40 Architecture: Processing 1 Billion API Requests with 99.99% Uptime Vibe Coding: A Workflow Guide (From Zero to SaaS) Most webhook security guides protect the wrong side. The scary part is delivery. Headless CMS for TanStack Start: Build a Blog with Cosmic EU Age Verification App "Hacked in 2 Minutes" — What Actually Happened Comfy Cloud’s delete function does not actually remove files Running AI Models on GPU Cloud Servers: A Beginner Guide Event-driven media intelligence with AWS Step Functions and Bedrock I scored 500 AI prompts across 8 quality dimensions — here's what broke How to Call Google Gemini API from Next.js (Free Tier, No Backend Needed) The Portal Protocol: Reclaiming Human Connection in the Age of AI How to Fix Your Team's Scattered Knowledge Problem With a Self-Hosted Forum Intro to tc Cloud Functors: A Graph-First Mental Model for the Modern Cloud Designing Multi-Tenant Backends With Both Ownership and Team Access I Built a Neumorphic CSS Library with 77+ Components — Here's What I Learned PostgreSQL Performance Optimization: Why Connection Pooling Is Critical at Scale Cómo construí un SaaS multi-rubro para gestionar expensas en Argentina con FastAPI + Vue 3 🚀 I Built an Ethical Hacking Scanner Tool – Open Source Project I Replaced /usage and /context in Claude Code With a Single Statusline A Pythonic Way to Handle Emails (IMAP/SMTP) with Auto-Discovery and AI-Ready Design I Collected 8.9 Million Polymarket Price Points — Here's What I Found About How Markets Really Move EcoTrack AI — Carbon Footprint Tracker & Dashboard Everyone's Using AI. No One Agrees How. 5 self-hosted ebook managers worth trying in 2026 Building Your First AI Agent with LangChain: From Chatbot to Autonomous Assistant
Rabbit Relay v1.0.0: Type-safe RabbitMQ for Node.js witho...
Sohaib Alqasem · 2026-06-25 · via DEV Community

I just released Rabbit Relay 1.0.0, the first stable release of a TypeScript-first RabbitMQ framework for Node.js.

Rabbit Relay is built on top of amqplib.

The main idea is simple:

Keep RabbitMQ explicit, but make it safer and cleaner to use in TypeScript services.

It keeps real RabbitMQ concepts visible:

  • exchanges
  • queues
  • bindings
  • routing keys
  • acknowledgements
  • retries
  • dead-letter queues
  • publisher confirms
  • topology ownership

It is not trying to turn RabbitMQ into magic function calls.

It is trying to make RabbitMQ easier to use correctly.


Why I built it

amqplib is powerful, but it is low-level.

In real services, teams often repeat the same boilerplate again and again:

  • create connection
  • create channel
  • assert exchanges
  • assert queues
  • bind queues
  • serialize messages
  • publish messages
  • consume messages
  • ACK/NACK messages
  • handle retries
  • configure DLQs
  • add correlation IDs
  • add health checks
  • decide who owns topology

That logic can become inconsistent across services.

One service handles retries one way.

Another forgets publisher confirms.

Another requeues forever.

Another uses routing keys differently.

Rabbit Relay is my attempt to make this layer consistent without hiding the RabbitMQ model.


A typed event

import { RabbitMQBroker, event } from "@bitspacerlabs/rabbit-relay";

type OrderCreated = {
  orderId: string;
  amount: number;
};

const orderCreated = event("order.created", "v1").of<OrderCreated>();

const broker = new RabbitMQBroker("orders.publisher");

const pub = await broker
  .queue("orders.publisher.q")
  .exchange("orders.ex", {
    exchangeType: "topic",
    publisherConfirms: true,
  });

await pub.produce(
  orderCreated({
    orderId: "o-1",
    amount: 42,
  })
);

await broker.close();

The RabbitMQ concepts are still visible:

orders.ex = exchange
orders.publisher.q = queue
topic = exchange type
publisherConfirms = wait for RabbitMQ broker acknowledgement

Rabbit Relay just gives this a cleaner TypeScript API.


Typed event contracts

Messaging systems often fail when message contracts become tribal knowledge.

Someone knows order.created has an orderId.

Someone else knows payment.processed has a transactionId.

But unless those contracts are visible in code, they are easy to break.

Rabbit Relay event factories make the event name, version, and payload type explicit:

const paymentProcessed = event("payment.processed", "v1").of<{
  orderId: string;
  transactionId: string;
  status: "paid";
}>();

Then publishing is type-checked:

await pub.produce(
  paymentProcessed({
    orderId: "o-1",
    transactionId: "txn-123",
    status: "paid",
  })
);

This does not replace schema validation or contract testing.

But it makes the common TypeScript workflow much safer.


A typed publish API

Rabbit Relay can also build a small typed publish API from event factories:

const send = event("send", "v1").of<{ message: string }>();

const api = pub.with({ send });

await api.send({
  message: "hello world",
});

The generated methods create the event and publish it.

So they are async publish methods and should be awaited.


Routing keys stay explicit

RabbitMQ topic routing is powerful, but easy to confuse.

In topic exchanges, values like order.* or # are usually binding patterns.

They are useful when binding a queue.

But they are usually not what you want to publish as the message routing key.

Rabbit Relay keeps this behavior explicit.

By default, Rabbit Relay publishes with the event name as the routing key:

const makeOrderCreated = event("order.created", "v1").of<OrderCreated>();

await pub.produce(
  makeOrderCreated({
    orderId: "o-1",
    amount: 42,
  })
); // routing key: "order.created"

If a concrete routing key is configured, Rabbit Relay can use it when publishing.

But if the configured routing key is a topic wildcard pattern like # or order.*, Rabbit Relay treats it as a binding pattern and continues publishing with the event name.

You can still override the publish routing key explicitly:

await pub.publish(eventEnvelope, {
  routingKey: "custom.key",
});


Consuming events

import { RabbitMQBroker, type EventEnvelope } from "@bitspacerlabs/rabbit-relay";

type OrderCreated = {
  orderId: string;
  amount: number;
};

const broker = new RabbitMQBroker("orders.consumer");

const sub = await broker
  .queue("orders.q")
  .exchange<{
    "order.created": EventEnvelope<OrderCreated>;
  }>("orders.ex", {
    exchangeType: "topic",
    routingKey: "order.*",
  });

sub.handle("order.created", async (_id, ev) => {
  console.log(ev.data.orderId);
});

await sub.consume({
  prefetch: 10,
  concurrency: 5,
});

The mental model stays the same:

producer -> exchange -> binding -> queue -> consumer


Production failure handling

The happy path is not enough.

Consumers fail.

Downstream services go down.

Databases timeout.

Messages can be delivered more than once.

Poison messages can block processing if failure handling is not designed carefully.

Rabbit Relay supports:

  • ACK on success
  • NACK and requeue
  • NACK and dead-letter
  • bounded retry
  • delayed retry
  • DLQ redrive

Example:

await sub.consume({
  prefetch: 10,
  concurrency: 5,
  onError: "retry",
  retry: {
    attempts: 3,
    delayMs: 5000,
    then: "dead-letter",
  },
});

This avoids two dangerous patterns:

  1. Losing failed messages silently.
  2. Requeueing forever and creating an infinite failure loop.

Operations features

Rabbit Relay 1.0.0 also includes:

  • lifecycle hooks
  • health checks
  • OpenTelemetry adapter
  • topology planning
  • topology validation
  • DLQ redrive

For example:

const plan = broker.planTopology();

console.log(plan);

Topology validation can passively check existing RabbitMQ infrastructure without modifying it.

That is useful when infrastructure owns the RabbitMQ topology and applications should only validate that required exchanges and queues exist.


Why not hide RabbitMQ?

Some libraries try to make messaging look like normal function calls.

That can be nice at first, but it often hides important operational details.

RabbitMQ concepts matter:

  • exchange type matters
  • routing key matters
  • queue durability matters
  • prefetch matters
  • ACK/NACK behavior matters
  • DLQs matter
  • publisher confirms matter
  • topology ownership matters

Rabbit Relay is designed around the idea that developers should understand these concepts, not avoid them.

The library should reduce boilerplate, not remove the mental model.


Install

npm i @bitspacerlabs/rabbit-relay

GitHub: github.com/bitspacerlabs/rabbit-relay

Docs: bitspacerlabs.github.io/rabbit-relay

npm: @bitspacerlabs/rabbit-relay


Final thought

RabbitMQ is already powerful.

Rabbit Relay is not trying to replace it.

It is trying to make RabbitMQ easier to use correctly in Node.js and TypeScript services, while keeping the real messaging concepts visible.

That is the core idea:

Type-safe RabbitMQ for Node.js, without hiding RabbitMQ.