Event Bus
RabbitMQ Event Bus
Reliable broker-backed event bus using RabbitMQ and amqplib with confirm-channel publishing and exponential-backoff reconnection.
The @noddde/rabbitmq package wraps amqplib to deliver events through RabbitMQ exchanges with confirm-channel publishing, durable queues, and indefinite mid-session reconnection.
Installation
yarn add @noddde/rabbitmq amqplib
yarn add -D @types/amqplibConfiguration
import { RabbitMqEventBus } from "@noddde/rabbitmq";
const eventBus = new RabbitMqEventBus({
url: "amqp://localhost:5672",
exchangeName: "noddde.events", // optional (this is the default)
exchangeType: "topic", // optional — "topic" (default) or "fanout"
queuePrefix: "noddde", // optional — queues become "noddde.AccountCreated"
prefetchCount: 10, // optional — backpressure control
resilience: {
// optional — connection resilience with exponential backoff
maxAttempts: 3,
initialDelayMs: 1000,
maxDelayMs: 30000,
},
});| Option | Type | Required | Default | Description |
|---|---|---|---|---|
url | string | Yes | --- | AMQP connection URL |
exchangeName | string | No | "noddde.events" | Exchange name for event publishing |
exchangeType | "topic" | "fanout" | No | "topic" | Exchange type |
queuePrefix | string | No | "noddde" | Queue name prefix |
prefetchCount | number | No | 10 | Maximum unacknowledged messages per consumer. Provides backpressure control |
resilience.maxAttempts | number | No | 3 | Maximum number of initial connection attempts. Mid-session reconnection retries indefinitely until close() is called |
resilience.initialDelayMs | number | No | 1000 | Initial delay between retries in milliseconds. Doubles on each retry |
resilience.maxDelayMs | number | No | 30000 | Maximum delay between retries in milliseconds |
logger | Logger | No | NodddeLogger("warn", "noddde:rabbitmq") | Framework logger instance from @noddde/core. All internal logging goes through this logger with structured context data |
How It Works
- Publishing: Events are serialized as JSON and published to the configured exchange with the event name as the routing key. Messages are marked
{ persistent: true }for durability. Whenevent.metadata.eventIdis present, it is set as the AMQPmessageIdproperty, giving consumers a stable identifier for retry tracking. The bus uses a confirm channel (createConfirmChannel) and awaitswaitForConfirms()after each publish, guaranteeing the broker has accepted the message beforedispatch()resolves. - Subscribing: Each event name gets a dedicated durable queue (
${queuePrefix}.${eventName}) bound to the exchange. Multiple handlers for the same event are invoked in parallel viaPromise.allSettled()— every handler runs to completion even when some fail, and each failure is logged individually with structured fields (see Handler Error Isolation). Consumers must be idempotent since handlers that completed before a sibling failure will re-execute on redelivery. - Delivery: At-least-once with manual acknowledgment. On handler success, the message is acked. On any handler failure, the first rejection is re-thrown after all siblings have settled, and the consume callback calls
channel.nack(msg, false, true)for requeue. - Poison message protection: Malformed messages that fail JSON deserialization are acknowledged and skipped, preventing infinite nack/requeue loops. If
resilience.maxRetriesis configured, messages that exceed the delivery count limit (tracked via an in-memory counter per message) are also acknowledged and discarded. The in-memory approach is used instead ofx-deathheaders becausex-deathrequires a dead-letter exchange to be configured. - Backpressure: The
prefetchCountoption limits how many unacknowledged messages the broker sends to this consumer, providing natural backpressure when handlers are slow. - Connection resilience: The
resilienceoption enables retry with exponential backoff on initial connection failure. Delay doubles on each attempt, capped atmaxDelayMs. If all attempts fail, the last error is thrown. After a successful connection,errorandclosehandlers are registered on the AMQP connection to detect unexpected disconnections. On unexpected close, the bus automatically attempts reconnection indefinitely using jittered exponential backoff (base delay doubles each attempt, capped atmaxDelayMs, with +-25% random jitter to prevent thundering herd). Unlike the initialconnect(), mid-session reconnection ignoresmaxAttemptsand retries until either the broker recovers orclose()is called. During reconnection,dispatch()rejects with a connection error. Once reconnected, the exchange and all consumers are re-established automatically. - Logging: All internal logging uses the framework
Loggerinterface from@noddde/core. Pass a customloggerin the config or accept the default (NodddeLogger("warn", "noddde:rabbitmq")). All log calls include structured context data (event name, error details).
Wiring with Domain
import { wireDomain } from "@noddde/engine";
import { RabbitMqEventBus } from "@noddde/rabbitmq";
const domain = await wireDomain(myDomain, {
buses: () => ({
eventBus: new RabbitMqEventBus({
url: "amqp://localhost:5672",
}),
}),
});