NestJS materials

Write an awesome doc for NestJS tips, tricks, notes, things which are not in the doc (or are but not really obvious) and experimented to figure them out and use them.

View on GitHub

Dead Letter Queue

How it Works

  1. RabbitMQ increases the x-delivery-count header with each retry automatically, no need for your majesty to do anything!
    • RabbitMQ increases this counter by one each time we throw an error in our handler.
    • We are CANNOT auto acknowledging messages.
  2. In @RabbitSubscribe decorator we should configure x-dead-letter-exchange and x-dead-letter-routing-key properties of a queue. This way it will know what it should do once it reaches the maximum retry count (we have to configure this as a policy separately).
  3. Lastly we need to create a policy for the queue to automatically send a message to the DLQ:
    • To do this we need to configure the delivery-limit policy (learn more here).
    • We cannot configure policies unfortunately with @golevelup/nestjs-rabbitmq or amqplib. But we can configure it like this:
      $ curl -u $RABBITMQ_DEFAULT_USER:$RABBITMQ_DEFAULT_PASS \
             -H "content-type: application/json" \
             -X PUT http://$RABBITMQ_HTTP_BASE_URL/api/policies/%2F/events-poison-limit \
             -d '{"pattern":"^events-queue$","definition":{"delivery-limit":3,"dead-letter-exchange":"events.dlx","dead-letter-routing-key":"events.dead-letter"},"priority":10,"apply-to":"queues"}'
      

      This is exactly what this.rabbitmqPolicyService.upsertDeliveryLimitPolicy(...) does for us. Another way of configuring it is using rabbitmqctl:

      $ rabbitmqctl set_policy --vhost / --apply-to queues events-poison-limit \
                    '^events-queue$' \
                    '{"delivery-limit": 3, "dead-letter-exchange":"events.dlx", "dead-letter-routing-key":"events.dead-letter"}' \
                    --priority 10
      
    • You can of course handle the retry and pushing the messages to the DLQ inside the errorHandler. But that is too much work.

[!NOTE]

If user triggers the reprocess and it fails we requeue the message in the DLQ. So client can trigger the reprocessing once more. The only reason this might happen is that we fail to publish the message back to the normal queue, or something else goes wrong before that (e.g. some assumptions turns out to be wrong).

[!TIP]

You can also use RabbitMQ shovels to move the messages from DLQ back to the original queue they came from in your NodeJS app as explain here. But the current implementation is also a common implementation which gives you more control over what should we do.

What is Shovel? It is a plugin that can move messages between queues automatically. But of course we need to first enable it.

How to start it

  1. pnpm install.
  2. cp .env.example .env.
  3. docker compose up -d.