rabbitmq-client

repository·master·Indexed 41 days ago

https://github.com/cody-greene/node-rabbitmq-client

A robust, typed, and dependency-free Node.js client library for RabbitMQ (0-9-1). Version 5.0.8 features automatic reconnection, re-subscription, and high-level ergonomic APIs for Consumers, Publishers, and RPC communication via RPCClient, serving as an alternative to amqplib.

Tokens
11.1K
Snippets
35
Records
45
Agent score
51%

What's inside rabbitmq-client

  1. Implement request-response communication with RPCClient

    master

    The RPCClient allows for request-response patterns between services using RabbitMQ.

    • RPC Server: Create a Consumer on a specific queue. The callback receives the request (req) and a reply function. Calling await reply(data) sends the response back.
    • RPC Client: Create an RPCClient and use .send(queue, message) to send a request and wait for the response.
    // rpc-server.js
    const rpcServer = rabbit.createConsumer({
      queue: 'my-rpc-queue'
    }, async (req, reply) => {
      console.log('request:', req.body)
      await reply('pong')
    })
    
    // rpc-client.js
    const rpcClient = rabbit.createRPCClient({confirm: true})
    const res = await rpcClient.send('my-rpc-queue', 'ping')
    console.log('response:', res.body) // pong
  2. Quick start with rabbitmq-client

    master

    To use rabbitmq-client, initialize a Connection with an AMQP URI. The library provides high-level Consumer and Publisher interfaces that handle automatic reconnection, re-subscription, and retries.

    Key lifecycle events for the Connection include error and connection (emitted when a connection is successfully established or re-established).

    import {Connection} from 'rabbitmq-client'
    
    // Initialize:
    const rabbit = new Connection('amqp://guest:guest@localhost:5672')
    rabbit.on('error', (err) => {
      console.log('RabbitMQ connection error', err)
    })
    rabbit.on('connection', () => {
      console.log('Connection successfully (re)established')
    })
  3. Control message acknowledgement with ConsumerStatus

    master

    The ConsumerHandler can return a ConsumerStatus to explicitly control how RabbitMQ acknowledges or rejects a message. This is useful for fine-grained control over message lifecycle.

    StatusValueResulting RabbitMQ CommandDescription
    ACK0BasicAckMessage is successfully processed.
    REQUEUE1BasicNack(requeue=true)Message is returned to the queue for retry.
    DROP2BasicNack(requeue=false)Message is discarded or sent to a dead-letter exchange.
    import { ConsumerStatus } from 'rabbitmq-client'
    
    // Inside your handler:
    if (isTransientError) {
      return ConsumerStatus.REQUEUE
    } else if (isFatalError) {
      return ConsumerStatus.DROP
    }
  4. Understand AsyncMessage and SyncMessage structures

    master

    The library uses specific interfaces to represent messages received from RabbitMQ:

    AsyncMessage

    Received via a consumer (e.g., Channel.basicConsume). It includes:

    • body: The actual message content (string, Buffer, or any).
    • deliveryTag: The server-assigned delivery tag.
    • exchange: The exchange the message was published to.
    • routingKey: The routing key used.
    • redelivered: Boolean indicating if the message was previously delivered.
    • HeaderFields: Metadata like contentType, correlationId, etc.

    SyncMessage

    Received via a synchronous pull (e.g., Channel.basicGet). It includes the same fields as AsyncMessage plus:

    • messageCount: The number of messages remaining in the queue.
  5. Use the Channel class for AMQP operations

    master

    The Channel class is a low-level interface for multiplexing a single TCP/IP connection into multiple lightweight logical connections. While you can use Channel directly, it is recommended to use higher-level abstractions like Consumer or Publisher for most use cases.

    Key capabilities include:

    • Declaring and managing queues and exchanges.
    • Publishing messages to exchanges or directly to queues.
    • Consuming messages via callbacks.
    • Managing publisher acknowledgements (confirms) or transactions.
    • Binding queues to exchanges.
    const rabbit = new Connection()
    
    // Will wait for the connection to establish and then create a Channel
    const ch = await rabbit.acquire()
    
    // Channels can emit some events too
    ch.on('close', () => {
      console.log('channel was closed')
    })
    
    // Create a queue for the duration of this connection
    await ch.queueDeclare({queue: 'my-queue'})
    
    // Enable publisher acknowledgements
    await ch.confirmSelect()
    
    const data = {title: 'just some object'}
    
    // Resolves when the data has been flushed through the socket or if
    // ch.confirmSelect() was called: will wait for an acknowledgement
    await ch.basicPublish({routingKey: 'my-queue'}, data)
    
    const msg = ch.basicGet('my-queue')
    console.log(msg)
    
    await ch.queueDelete('my-queue')
    
    // It's your responsibility to close any acquired channels
    await ch.close()
  6. Configure connection via URL parameters

    master

    When using a connection string (url), you can pass specific RabbitMQ configuration parameters as query parameters. These will override the default values or the values provided in the object.

    Supported URL parameters:

    • heartbeat: Sets the heartbeat interval in seconds.
    • connection_timeout: Sets the connection timeout in milliseconds.
    • channel_max: Sets the maximum number of channels.

    Note: The heartbeat value must be $\ge 0$. The channel_max value is clamped between $1$ and the value provided in maxChannels.

    // Example using URL parameters
    const url = 'amqp://guest:guest@localhost:5672/?heartbeat=30&connection_timeout=5000&channel_max=100';
  7. Create a consumer with Connection.createConsumer()

    master

    Use Connection.createConsumer() to set up a high-level consumer. This method automatically handles channel creation, queue/exchange declaration, bindings, and QoS settings. If the connection resets, the consumer will automatically re-run its setup on a new channel using exponential backoff.

    Key Behaviors:

    • Automatic Setup: Declares queues, exchanges, and bindings before starting consumption.
    • Error Handling: If your handler throws an error, the message is rejected (Nack) and optionally requeued based on ConsumerProps.requeue.
    • Concurrency: Controls how many messages are processed simultaneously. For optimal performance, set concurrency to $X$ and qos.prefetchCount to $2X$.
    • RPC Support: The second argument of the handler (reply) allows you to respond to RPC-style messages easily.
    const sub = rabbit.createConsumer(
      { queue: 'my-queue' }, 
      async (msg, reply) => {
        console.log(msg)
        // ... do some work ...
    
        // optionally reply to an RPC-type message
        await reply('my-response-data')
    
        // optionally return a status code to control ACK/NACK
        if (somethingBad) {
          return ConsumerStatus.DROP
        }
      }
    )
    
    sub.on('error', (err) => {
      console.log('consumer error (my-queue)', err)
    })
    
    // when closing the application
    await sub.close()
  8. Use raw AMQP Channels as a last resort

    master

    If the high-level APIs are insufficient, you can acquire a raw AMQP Channel using rabbit.acquire().

    Warning: When using raw channels, you are responsible for:

    1. Handling connection/channel loss (re-declaring queues, exchanges, etc.).
    2. Closing the channel manually using await ch.close() to prevent resource leaks.

    Raw channels can emit events like close.

    // Will wait for the connection to establish and then create a Channel
    const ch = await rabbit.acquire()
    
    ch.on('close', () => {
      console.log('channel was closed')
    })
    
    const msg = ch.basicGet('my-queue')
    console.log(msg)
    
    // It's your responsibility to close any acquired channels
    await ch.close()
  9. Manage queues and exchanges via Connection

    master

    While it is recommended to use the declarative Consumer or Publisher APIs for managing infrastructure, you can use the Connection interface for one-off management tasks:

    • queueDeclare({queue, exclusive, passive, ...})
    • exchangeDeclare({queue, exchange, type, ...})
    • queueBind({queue, exchange, ...})

    Note: queueDeclare with passive: true returns an object containing the messageCount.

    const rabbit = new Connection()
    
    await rabbit.queueDeclare({queue: 'my-queue', exclusive: true})
    
    await rabbit.exchangeDeclare({queue: 'my-queue', exchange: 'my-exchange', type: 'topic'})
    
    await rabbit.queueBind({queue: 'my-queue', exchange: 'my-exchange'})
    
    const {messageCount} = await rabbit.queueDeclare({queue: 'my-queue', passive: true})
  10. Publish messages using the Publisher API

    master

    The Publisher API provides a reliable way to send messages. It supports publish confirmations and automatic retries.

    Configuration options:

    • confirm: Set to true to enable publish confirmations.
    • maxAttempts: Number of retries for publishing.
    • exchanges: An array of exchanges to ensure exist.

    You can publish to a specific exchange using a routing key, or publish directly to a queue name.

    const pub = rabbit.createPublisher({
      confirm: true,
      maxAttempts: 2,
      exchanges: [{exchange: 'my-events', type: 'topic'}]
    })
    
    // Publish to an exchange with a routing key
    await pub.send(
      {exchange: 'my-events', routingKey: 'users.visit'},
      {id: 1, name: 'Alan Turing'}
    )
    
    // Or publish directly to a queue
    await pub.send('user-events', {id: 1, name: 'Alan Turing'})
  11. Consume messages using the Consumer API

    master

    The Consumer API is a robust way to listen to queues. It abstracts away the complexity of re-declaring queues, exchanges, and bindings if the connection or channel is lost.

    When the callback function completes successfully, the message is automatically acknowledged (BasicAck). If the callback throws an error, the message is rejected (BasicNack) and may be requeued or sent to a dead-letter exchange. You can also return a status code from the callback to control this behavior manually.

    Common configuration options include:

    • queue: The name of the queue to consume from.
    • queueOptions: Options for queue declaration (e.g., {durable: true}).
    • qos: Quality of Service settings (e.g., {prefetchCount: 2}).
    • exchanges: An array of exchanges to ensure exist.
    • queueBindings: An array of bindings to ensure exist between exchanges and the queue.
    const sub = rabbit.createConsumer({
      queue: 'user-events',
      queueOptions: {durable: true},
      qos: {prefetchCount: 2},
      exchanges: [{exchange: 'my-events', type: 'topic'}],
      queueBindings: [{exchange: 'my-events', routingKey: 'users.*'}],
    }, async (msg) => {
      console.log('received message (user-events)', msg)
    })
    
    sub.on('error', (err) => {
      console.log('consumer error (user-events)', err)
    })
  12. Configure Publisher behavior with PublisherProps

    master

    When using createPublisher, you can pass a PublisherProps object to define the resilience and topology of the publisher.

    PropertyTypeDescription
    confirmbooleanEnable publish-confirm mode (requires channel.confirmSelect()).
    maxAttemptsnumberMaximum number of publish attempts before failing. Defaults to 1.
    onReturn(msg: ReturnedMessage) => voidCallback for basic.return events.
    queuesArray<MethodParams[Cmd.QueueDeclare]>Queues to declare on setup/reconnect.
    exchangesArray<MethodParams[Cmd.ExchangeDeclare]>Exchanges to declare on setup/reconnect.
    queueBindingsArray<MethodParams[Cmd.QueueBind]>Queue-to-exchange bindings to declare on setup/reconnect.
    exchangeBindingsArray<MethodParams[Cmd.ExchangeBind]>Exchange-to-exchange bindings to declare on setup/reconnect.