<?xml version="1.0" encoding="UTF-8"?>
<rss version="2.0" xmlns:atom="http://www.w3.org/2005/Atom" xmlns:dc="http://purl.org/dc/elements/1.1/">
  <channel>
    <title>DEV Community: Hailay Gebreslasie Gebremeskel</title>
    <description>The latest articles on DEV Community by Hailay Gebreslasie Gebremeskel (@hailay).</description>
    <link>https://dev.to/hailay</link>
    <image>
      <url>https://media2.dev.to/dynamic/image/width=90,height=90,fit=cover,gravity=auto,format=auto/https:%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Fuser%2Fprofile_image%2F3892585%2Fd02c8459-7937-449f-8ff3-3ba84edee374.png</url>
      <title>DEV Community: Hailay Gebreslasie Gebremeskel</title>
      <link>https://dev.to/hailay</link>
    </image>
    <atom:link rel="self" type="application/rss+xml" href="https://dev.to/feed/hailay"/>
    <language>en</language>
    <item>
      <title>Migrating a LavinMQ Node.js app from `amqplib` to `amqp-client.js`</title>
      <dc:creator>Hailay Gebreslasie Gebremeskel</dc:creator>
      <pubDate>Wed, 07 Oct 2026 16:02:26 +0000</pubDate>
      <link>https://dev.to/hailay/migrating-a-lavinmq-nodejs-app-from-amqplib-to-amqp-clientjs-4o63</link>
      <guid>https://dev.to/hailay/migrating-a-lavinmq-nodejs-app-from-amqplib-to-amqp-clientjs-4o63</guid>
      <description>&lt;p&gt;If you have an existing Node.js application using &lt;code&gt;amqplib&lt;/code&gt;, changing AMQP clients might sound like rewriting a large part of your application.&lt;/p&gt;

&lt;p&gt;I wanted to see how much actually had to change.&lt;/p&gt;

&lt;p&gt;I built a small banking application that publishes transactions to LavinMQ, processes them in a worker, retries temporary failures, dead-letters permanent failures, and protects against duplicate processing.&lt;/p&gt;

&lt;p&gt;The application separates the banking logic from the client-specific messaging code through a &lt;code&gt;broker.js&lt;/code&gt; layer.&lt;/p&gt;

&lt;p&gt;I first implemented that layer with &lt;code&gt;amqplib&lt;/code&gt;, then migrated it to &lt;code&gt;amqp-client.js&lt;/code&gt;, an AMQP client developed by CloudAMQP.&lt;/p&gt;

&lt;p&gt;The interesting result was that &lt;strong&gt;most of the application didn't need to change&lt;/strong&gt;.&lt;/p&gt;

&lt;h2&gt;
  
  
  What stayed the same?
&lt;/h2&gt;

&lt;p&gt;The migration didn't require any application redesign.&lt;/p&gt;

&lt;p&gt;The following parts stayed the same:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Banking logic&lt;/li&gt;
&lt;li&gt;Queue and exchange topology&lt;/li&gt;
&lt;li&gt;Retry strategy&lt;/li&gt;
&lt;li&gt;Dead-letter handling&lt;/li&gt;
&lt;li&gt;Idempotency logic&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;The worker still makes the same application-level decisions: process the transaction, retry a temporary failure, dead-letter a permanent failure, or acknowledge a transaction that has already been processed.&lt;/p&gt;

&lt;p&gt;The client-specific messaging operations are kept behind &lt;code&gt;broker.js&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;Conceptually, the application looks like this:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Application logic
       |
       v
   broker.js
       |
       v
  AMQP client
       |
       v
    LavinMQ
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This separation became particularly useful when I started replacing one AMQP client with the other.&lt;/p&gt;

&lt;h2&gt;
  
  
  What actually changed?
&lt;/h2&gt;

&lt;p&gt;Most of the changes were isolated to &lt;code&gt;broker.js&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;That's where the differences between the two clients became much clearer:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Establishing connections&lt;/li&gt;
&lt;li&gt;Publishing messages&lt;/li&gt;
&lt;li&gt;Consuming messages&lt;/li&gt;
&lt;li&gt;Acknowledging messages&lt;/li&gt;
&lt;li&gt;Managing connection recovery&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;For example, the worker can interact with the broker through the same small interface:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nx"&gt;broker&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;connect&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;
&lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nx"&gt;broker&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;subscribe&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;handleMessage&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;When processing a transaction, it can also use operations such as:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nx"&gt;broker&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;retry&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
  &lt;span class="nx"&gt;transaction&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="nx"&gt;retries&lt;/span&gt; &lt;span class="o"&gt;+&lt;/span&gt; &lt;span class="mi"&gt;1&lt;/span&gt;
&lt;span class="p"&gt;);&lt;/span&gt;

&lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nx"&gt;broker&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;sendToDeadQueue&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
  &lt;span class="nx"&gt;transaction&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="nx"&gt;error&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="nx"&gt;retries&lt;/span&gt;
&lt;span class="p"&gt;);&lt;/span&gt;

&lt;span class="nx"&gt;broker&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;acknowledge&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;message&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The worker decides &lt;strong&gt;what should happen&lt;/strong&gt; to a transaction.&lt;/p&gt;

&lt;p&gt;The broker layer handles &lt;strong&gt;how the corresponding messaging operation is performed&lt;/strong&gt; using the AMQP client.&lt;/p&gt;

&lt;p&gt;That meant I could change the implementation behind &lt;code&gt;broker.js&lt;/code&gt; without redesigning the banking application's reliability logic.&lt;/p&gt;

&lt;h2&gt;
  
  
  Connection recovery was the biggest difference
&lt;/h2&gt;

&lt;p&gt;Connection recovery was where I noticed the biggest difference between the two approaches.&lt;/p&gt;

&lt;p&gt;With &lt;code&gt;amqplib&lt;/code&gt;, I handled recovery in the broker layer.&lt;/p&gt;

&lt;p&gt;When the connection is lost, the application needs to detect the closed connection, reconnect, create a new channel, and restore the consumer.&lt;/p&gt;

&lt;p&gt;Conceptually:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Connection lost
      |
      v
Detect connection close
      |
      v
Wait and reconnect
      |
      v
Create a new channel
      |
      v
Restore the consumer
      |
      v
Continue processing
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This works, but the application owns the recovery behavior.&lt;/p&gt;

&lt;p&gt;With the high-level &lt;code&gt;AMQPSession&lt;/code&gt; API in &lt;code&gt;amqp-client.js&lt;/code&gt;, more of that connection lifecycle is handled by the client.&lt;/p&gt;

&lt;p&gt;The session can reconnect when the connection is interrupted and restore its subscriptions after the connection is recovered.&lt;/p&gt;

&lt;p&gt;Conceptually:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Connection lost
      |
      v
AMQPSession detects it
      |
      v
Reconnect
      |
      v
Restore subscription
      |
      v
Continue processing
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The important difference isn't that one client can recover and the other cannot.&lt;/p&gt;

&lt;p&gt;Both can be used to build an application that recovers from connection failures.&lt;/p&gt;

&lt;p&gt;The difference is &lt;strong&gt;where the responsibility for recovery lives&lt;/strong&gt;.&lt;/p&gt;

&lt;p&gt;With &lt;code&gt;amqplib&lt;/code&gt;, I implemented it in the broker layer.&lt;/p&gt;

&lt;p&gt;With the high-level &lt;code&gt;AMQPSession&lt;/code&gt; API, connection and subscription recovery are handled by the client.&lt;/p&gt;

&lt;h2&gt;
  
  
  Reliability didn't move into the client
&lt;/h2&gt;

&lt;p&gt;One thing I found particularly interesting was that changing clients didn't change most of the reliability patterns in the application.&lt;/p&gt;

&lt;p&gt;Retries were still retries.&lt;/p&gt;

&lt;p&gt;Dead-lettering was still dead-lettering.&lt;/p&gt;

&lt;p&gt;Messages still needed acknowledgements.&lt;/p&gt;

&lt;p&gt;Queues still needed the appropriate durability settings.&lt;/p&gt;

&lt;p&gt;Transactions still needed to be processed idempotently to protect against duplicate delivery.&lt;/p&gt;

&lt;p&gt;Those concerns weren't suddenly solved by changing the AMQP client.&lt;/p&gt;

&lt;p&gt;The major difference I observed was how much connection lifecycle management the application had to handle itself.&lt;/p&gt;

&lt;h2&gt;
  
  
  The main takeaway
&lt;/h2&gt;

&lt;p&gt;The migration reinforced something I hadn't fully appreciated when I started the experiment:&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;Changing an AMQP client doesn't necessarily mean changing your application's reliability design.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;By keeping client-specific messaging code behind a broker layer, I could migrate from &lt;code&gt;amqplib&lt;/code&gt; to &lt;code&gt;amqp-client.js&lt;/code&gt; without rewriting the banking logic.&lt;/p&gt;

&lt;p&gt;The business behavior remained the same.&lt;/p&gt;

&lt;p&gt;The messaging architecture remained the same.&lt;/p&gt;

&lt;p&gt;The implementation behind &lt;code&gt;broker.js&lt;/code&gt; changed.&lt;/p&gt;

&lt;p&gt;I wrote a full step-by-step migration guide covering the changes to connections, publishing, consuming, acknowledgements, and connection recovery:&lt;/p&gt;

&lt;p&gt;👉 &lt;a href="https://www.cloudamqp.com/blog/lavinmq-nodejs-migration/" rel="noopener noreferrer"&gt;Read the full migration guide on CloudAMQP&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;The complete banking demo contains both implementations if you want to compare the code or run the experiment yourself:&lt;/p&gt;

&lt;p&gt;👉 &lt;a href="https://github.com/cloudamqp/lavinmq-demos/tree/main/nodejs-clients-demo" rel="noopener noreferrer"&gt;View the Node.js clients demo on GitHub&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;I'd be interested to hear how others approach this.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Where does connection recovery live in your Node.js messaging applications — in your application code, a wrapper around the client, or the client library itself?&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;And if you've migrated between AMQP clients before, what part of the migration caused the most work?&lt;/p&gt;

&lt;p&gt;Feel free to leave questions about the implementation too.&lt;/p&gt;

</description>
      <category>lavinmq</category>
      <category>messaging</category>
      <category>node</category>
      <category>pubsub</category>
    </item>
  </channel>
</rss>
