@bgaldino/nestjs-rabbitmq 1.5.1 → 2.0.0-beta.3
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +432 -322
- package/dist/amqp-connection-manager.d.ts +15 -18
- package/dist/amqp-connection-manager.js +98 -143
- package/dist/amqp-connection-manager.js.map +1 -1
- package/dist/class-discovery.d.ts +13 -0
- package/dist/class-discovery.js +78 -0
- package/dist/class-discovery.js.map +1 -0
- package/dist/connection-factory.d.ts +20 -0
- package/dist/connection-factory.js +106 -0
- package/dist/connection-factory.js.map +1 -0
- package/dist/helper.d.ts +1 -1
- package/dist/helper.js +3 -7
- package/dist/helper.js.map +1 -1
- package/dist/index.d.ts +4 -2
- package/dist/index.js +8 -1
- package/dist/index.js.map +1 -1
- package/dist/rabbit-consumer.decorator.d.ts +7 -0
- package/dist/rabbit-consumer.decorator.js +13 -0
- package/dist/rabbit-consumer.decorator.js.map +1 -0
- package/dist/rabbitmq-consumer.d.ts +19 -0
- package/dist/rabbitmq-consumer.js +183 -0
- package/dist/rabbitmq-consumer.js.map +1 -0
- package/dist/rabbitmq-retry-handler.d.ts +10 -0
- package/dist/rabbitmq-retry-handler.js +46 -0
- package/dist/rabbitmq-retry-handler.js.map +1 -0
- package/dist/rabbitmq-service.d.ts +12 -9
- package/dist/rabbitmq-service.js +45 -37
- package/dist/rabbitmq-service.js.map +1 -1
- package/dist/rabbitmq.constants.d.ts +1 -0
- package/dist/rabbitmq.constants.js +5 -0
- package/dist/rabbitmq.constants.js.map +1 -0
- package/dist/rabbitmq.interfaces.d.ts +32 -10
- package/dist/rabbitmq.module.d.ts +7 -7
- package/dist/rabbitmq.module.js +53 -7
- package/dist/rabbitmq.module.js.map +1 -1
- package/dist/rabbitmq.types.d.ts +67 -29
- package/dist/rabbitmq.types.js +4 -0
- package/dist/rabbitmq.types.js.map +1 -1
- package/dist/tsconfig.tsbuildinfo +1 -1
- package/package.json +12 -10
- package/dist/rabbitmq-consumers.d.ts +0 -19
- package/dist/rabbitmq-consumers.js +0 -210
- package/dist/rabbitmq-consumers.js.map +0 -1
package/README.md
CHANGED
|
@@ -1,489 +1,599 @@
|
|
|
1
1
|
# @bgaldino/nestjs-rabbitmq
|
|
2
2
|
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
- [
|
|
8
|
-
- [
|
|
9
|
-
|
|
10
|
-
- [
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
- [
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
- [
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
- [
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
- [
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
- [
|
|
32
|
-
- [
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
## Description
|
|
38
|
-
|
|
39
|
-
This module features an opinionated way of configuring the RabbitMQ connection
|
|
40
|
-
by using a configuration file that describes the behaviour of all publishers
|
|
41
|
-
and consumers present in your project
|
|
42
|
-
|
|
43
|
-
## Motivation
|
|
44
|
-
|
|
45
|
-
I wanted to have a central place where its easier to open the project and see
|
|
46
|
-
the declared behaviour of the RabbitMQ instance for that project, without having
|
|
47
|
-
to look around for NestJS annotations, microservices or whatever. Simply to open,
|
|
48
|
-
go to the configuration file and know exactly what I'm looking at and what I should
|
|
49
|
-
expect.
|
|
50
|
-
|
|
51
|
-
### Requirements
|
|
52
|
-
|
|
53
|
-
- @nestjs/common: ">9"
|
|
54
|
-
- An RabbitMQ instance with the
|
|
55
|
-
[RabbitMQ Delayed Message Plugin](https://github.com/rabbitmq/rabbitmq-delayed-message-exchange) installed
|
|
56
|
-
|
|
57
|
-
### Instalation
|
|
58
|
-
|
|
59
|
-
### PNPM
|
|
3
|
+
An opinionated NestJS module for RabbitMQ with built-in retry strategies, dead letter queues, and quorum queue support.
|
|
4
|
+
|
|
5
|
+
## Table of Contents
|
|
6
|
+
|
|
7
|
+
- [Installation](#installation)
|
|
8
|
+
- [Requirements](#requirements)
|
|
9
|
+
- [Getting Started](#getting-started)
|
|
10
|
+
- [forRoot](#forroot)
|
|
11
|
+
- [forRootAsync](#forrootasync)
|
|
12
|
+
- [Consumers](#consumers)
|
|
13
|
+
- [Decorator-based consumers](#decorator-based-consumers)
|
|
14
|
+
- [Config-based consumers](#config-based-consumers)
|
|
15
|
+
- [Mixed usage](#mixed-usage)
|
|
16
|
+
- [Handler signature](#handler-signature)
|
|
17
|
+
- [Consumer groups](#consumer-groups)
|
|
18
|
+
- [Publishers](#publishers)
|
|
19
|
+
- [Publishing messages](#publishing-messages)
|
|
20
|
+
- [Typed publishing](#typed-publishing)
|
|
21
|
+
- [Multi-vhost Connections](#multi-vhost-connections)
|
|
22
|
+
- [Named connections](#named-connections)
|
|
23
|
+
- [Targeting a connection from consumers](#targeting-a-connection-from-consumers)
|
|
24
|
+
- [Publishing to a specific connection](#publishing-to-a-specific-connection)
|
|
25
|
+
- [Retry Strategy](#retry-strategy)
|
|
26
|
+
- [Dead Letter Strategy](#dead-letter-strategy)
|
|
27
|
+
- [Disabling the automatic ack](#disabling-the-automatic-ack)
|
|
28
|
+
- [Custom Header Metadata](#custom-header-metadata)
|
|
29
|
+
- [Extra Options](#extra-options)
|
|
30
|
+
- [Consumer manual loading](#consumer-manual-loading)
|
|
31
|
+
- [Message inspection and logging](#message-inspection-and-logging)
|
|
32
|
+
- [Health check](#health-check)
|
|
33
|
+
- [Building locally](#building-locally)
|
|
34
|
+
- [License](#license)
|
|
35
|
+
|
|
36
|
+
## Installation
|
|
60
37
|
|
|
61
38
|
```shell
|
|
62
39
|
pnpm add @bgaldino/nestjs-rabbitmq
|
|
63
40
|
```
|
|
64
41
|
|
|
65
|
-
**YARN**
|
|
66
|
-
|
|
67
42
|
```shell
|
|
68
43
|
yarn add @bgaldino/nestjs-rabbitmq
|
|
69
44
|
```
|
|
70
45
|
|
|
71
|
-
**NPM**
|
|
72
|
-
|
|
73
46
|
```shell
|
|
74
47
|
npm add @bgaldino/nestjs-rabbitmq
|
|
75
48
|
```
|
|
76
49
|
|
|
50
|
+
## Requirements
|
|
51
|
+
|
|
52
|
+
- `@nestjs/common` and `@nestjs/core` version 9 or above
|
|
53
|
+
- RabbitMQ 3.10+ (quorum queue per-message TTL support)
|
|
54
|
+
|
|
55
|
+
No additional plugins are required. The retry mechanism uses native message TTL
|
|
56
|
+
and dead letter exchanges instead of the delayed message plugin.
|
|
57
|
+
|
|
77
58
|
## Getting Started
|
|
78
59
|
|
|
79
|
-
|
|
60
|
+
The `RabbitMQModule` is marked as `@Global`, so importing it once is enough
|
|
61
|
+
to inject `RabbitMQService` anywhere in your application.
|
|
62
|
+
|
|
63
|
+
### forRoot
|
|
80
64
|
|
|
81
|
-
|
|
65
|
+
For simple setups where the configuration is static:
|
|
82
66
|
|
|
83
67
|
```typescript
|
|
84
68
|
import { RabbitMQModule } from '@bgaldino/nestjs-rabbitmq';
|
|
85
|
-
import { RabbitOptions } from '@bgaldino/nestjs-rabbitmq';
|
|
86
69
|
|
|
87
70
|
@Module({
|
|
88
71
|
imports: [
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
72
|
+
RabbitMQModule.forRoot({
|
|
73
|
+
connectionString: 'amqp://user:password@localhost:5672/vhost',
|
|
74
|
+
delayExchangeName: 'my_app',
|
|
75
|
+
assertExchanges: [
|
|
76
|
+
{ name: 'orders', type: 'topic' },
|
|
77
|
+
{ name: 'notifications', type: 'fanout' },
|
|
78
|
+
],
|
|
79
|
+
}),
|
|
80
|
+
],
|
|
93
81
|
})
|
|
94
82
|
export class AppModule {}
|
|
95
83
|
```
|
|
96
84
|
|
|
97
|
-
|
|
98
|
-
to allow the injection of the `RabbitMQService`
|
|
85
|
+
### forRootAsync
|
|
99
86
|
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
Create a `rabbitmq.config.ts` or whatever the name you prefer containing the
|
|
103
|
-
minimum configuration:
|
|
87
|
+
When you need to inject dependencies or resolve configuration asynchronously:
|
|
104
88
|
|
|
105
89
|
```typescript
|
|
106
|
-
import {
|
|
107
|
-
import {
|
|
108
|
-
RabbitMQModuleOptions,
|
|
109
|
-
RabbitOptionsFactory,
|
|
110
|
-
} from "@bgaldino/nestjs-rabbitmq";
|
|
90
|
+
import { RabbitMQModule, RabbitMQOptionsFactory, ModuleOptions } from '@bgaldino/nestjs-rabbitmq';
|
|
111
91
|
|
|
112
92
|
@Injectable()
|
|
113
|
-
|
|
114
|
-
|
|
93
|
+
class RabbitConfig implements RabbitMQOptionsFactory {
|
|
94
|
+
constructor(private readonly configService: ConfigService) {}
|
|
95
|
+
|
|
96
|
+
createRabbitOptions(): ModuleOptions {
|
|
115
97
|
return {
|
|
116
|
-
connectionString:
|
|
117
|
-
delayExchangeName:
|
|
118
|
-
assertExchanges: [
|
|
119
|
-
|
|
98
|
+
connectionString: this.configService.get('RABBIT_URL'),
|
|
99
|
+
delayExchangeName: 'my_app',
|
|
100
|
+
assertExchanges: [
|
|
101
|
+
{ name: 'orders', type: 'topic' },
|
|
102
|
+
],
|
|
120
103
|
};
|
|
121
104
|
}
|
|
122
105
|
}
|
|
123
|
-
```
|
|
124
106
|
|
|
125
|
-
|
|
107
|
+
@Module({
|
|
108
|
+
imports: [
|
|
109
|
+
RabbitMQModule.forRootAsync({
|
|
110
|
+
useClass: RabbitConfig,
|
|
111
|
+
imports: [ConfigModule],
|
|
112
|
+
}),
|
|
113
|
+
],
|
|
114
|
+
})
|
|
115
|
+
export class AppModule {}
|
|
116
|
+
```
|
|
126
117
|
|
|
127
|
-
|
|
118
|
+
You can also use `useFactory` directly:
|
|
128
119
|
|
|
129
120
|
```typescript
|
|
130
|
-
|
|
131
|
-
{
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
],
|
|
121
|
+
RabbitMQModule.forRootAsync({
|
|
122
|
+
useFactory: (configService: ConfigService) => ({
|
|
123
|
+
connectionString: configService.get('RABBIT_URL'),
|
|
124
|
+
delayExchangeName: 'my_app',
|
|
125
|
+
assertExchanges: [],
|
|
126
|
+
}),
|
|
127
|
+
inject: [ConfigService],
|
|
128
|
+
imports: [ConfigModule],
|
|
129
|
+
})
|
|
138
130
|
```
|
|
139
131
|
|
|
140
|
-
|
|
141
|
-
and each entry will asserted against the RabbitMQ connected server.
|
|
132
|
+
## Consumers
|
|
142
133
|
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
|
|
134
|
+
There are two ways to register consumers: decorators and config. Both can be
|
|
135
|
+
used simultaneously and produce the same result at runtime. The library
|
|
136
|
+
validates that no duplicate queue names exist across both sources.
|
|
146
137
|
|
|
147
|
-
|
|
138
|
+
All queues are created as [quorum queues](https://www.rabbitmq.com/docs/quorum-queues)
|
|
139
|
+
by default. Consumers do not create exchanges, they only bind to exchanges
|
|
140
|
+
that already exist (declared via `assertExchanges`).
|
|
148
141
|
|
|
149
|
-
|
|
142
|
+
### Decorator-based consumers
|
|
143
|
+
|
|
144
|
+
Decorate any method with `@RabbitConsumer()` and the library will
|
|
145
|
+
automatically discover and wire it up during application bootstrap.
|
|
146
|
+
This is the recommended approach for most use cases since it keeps the
|
|
147
|
+
consumer logic located with the handler.
|
|
150
148
|
|
|
151
149
|
```typescript
|
|
152
|
-
import { Injectable } from
|
|
153
|
-
import {
|
|
150
|
+
import { Injectable } from '@nestjs/common';
|
|
151
|
+
import { RabbitConsumer, ConsumerOptions, MessageParams } from '@bgaldino/nestjs-rabbitmq';
|
|
154
152
|
|
|
155
153
|
@Injectable()
|
|
156
|
-
export class
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
|
|
160
|
-
|
|
161
|
-
|
|
162
|
-
}
|
|
163
|
-
|
|
164
|
-
//
|
|
165
|
-
|
|
166
|
-
async publishMeTyped() {
|
|
167
|
-
const isPublished = await this.rabbitMQService.publish<CustomType>('exchange_name', 'routing_key', {});
|
|
168
|
-
//This will return an error if the object is not properly typed
|
|
154
|
+
export class OrderService {
|
|
155
|
+
@RabbitConsumer({
|
|
156
|
+
queue: 'order.created',
|
|
157
|
+
exchangeName: 'orders',
|
|
158
|
+
routingKey: 'order.created',
|
|
159
|
+
prefetch: 5,
|
|
160
|
+
})
|
|
161
|
+
async handleOrderCreated(content: OrderPayload, params?: MessageParams) {
|
|
162
|
+
// handle message
|
|
163
|
+
}
|
|
169
164
|
}
|
|
170
165
|
```
|
|
171
166
|
|
|
172
|
-
The
|
|
173
|
-
|
|
174
|
-
the promise.
|
|
167
|
+
The provider must be registered in a NestJS module. The library scans all
|
|
168
|
+
providers and controllers for decorated methods.
|
|
175
169
|
|
|
176
|
-
###
|
|
170
|
+
### Config-based consumers
|
|
177
171
|
|
|
178
|
-
|
|
179
|
-
|
|
180
|
-
|
|
181
|
-
|
|
182
|
-
Inside the configuration file you can declare your consumers on the section
|
|
183
|
-
`consumerChannels`. This list of `RabbitMQConsumerChannel` will be evaluated
|
|
184
|
-
and each entry will try to create the queue and bind it to the declared
|
|
185
|
-
exchange.
|
|
186
|
-
|
|
187
|
-
**Example:**
|
|
172
|
+
If you prefer centralized configuration, or need to conditionally register
|
|
173
|
+
consumers based on environment, you can use the `consumerChannels` array.
|
|
174
|
+
The `defineRabbitConsumer` helper provides type safety:
|
|
188
175
|
|
|
189
176
|
```typescript
|
|
190
|
-
|
|
191
|
-
|
|
192
|
-
|
|
193
|
-
|
|
194
|
-
|
|
195
|
-
|
|
196
|
-
|
|
197
|
-
|
|
198
|
-
|
|
199
|
-
|
|
200
|
-
|
|
201
|
-
|
|
202
|
-
|
|
203
|
-
|
|
204
|
-
|
|
205
|
-
|
|
206
|
-
|
|
207
|
-
|
|
208
|
-
|
|
209
|
-
messageHandler: this.consumerService.messageHandler.bind(
|
|
210
|
-
this.consumerService,
|
|
211
|
-
),
|
|
212
|
-
},
|
|
213
|
-
]
|
|
214
|
-
}
|
|
215
|
-
}
|
|
177
|
+
import { defineRabbitConsumer } from '@bgaldino/nestjs-rabbitmq';
|
|
178
|
+
|
|
179
|
+
RabbitMQModule.forRoot({
|
|
180
|
+
connectionString: 'amqp://localhost',
|
|
181
|
+
delayExchangeName: 'my_app',
|
|
182
|
+
assertExchanges: [{ name: 'orders', type: 'topic' }],
|
|
183
|
+
consumerChannels: [
|
|
184
|
+
defineRabbitConsumer({
|
|
185
|
+
queue: 'order.created',
|
|
186
|
+
exchangeName: 'orders',
|
|
187
|
+
routingKey: 'order.created',
|
|
188
|
+
prefetch: 5,
|
|
189
|
+
handler: {
|
|
190
|
+
provider: OrderService,
|
|
191
|
+
methodName: 'handleOrderCreated',
|
|
192
|
+
},
|
|
193
|
+
}),
|
|
194
|
+
],
|
|
195
|
+
})
|
|
216
196
|
```
|
|
217
197
|
|
|
218
|
-
The
|
|
219
|
-
|
|
220
|
-
misconfigurations.
|
|
198
|
+
The library resolves the provider instance automatically. No need to manually
|
|
199
|
+
bind `this` or inject the service into your config.
|
|
221
200
|
|
|
222
|
-
|
|
223
|
-
multiple keys to the same queue/callback
|
|
201
|
+
### Mixed usage
|
|
224
202
|
|
|
225
|
-
|
|
203
|
+
Both approaches can coexist. A common pattern is using decorators for static
|
|
204
|
+
consumers and config for conditional ones:
|
|
226
205
|
|
|
227
|
-
|
|
228
|
-
|
|
229
|
-
|
|
230
|
-
|
|
206
|
+
```typescript
|
|
207
|
+
// Static consumer via decorator
|
|
208
|
+
@RabbitConsumer({ queue: 'audit.log', exchangeName: 'events', routingKey: '#' })
|
|
209
|
+
async auditLog(content: any) { ... }
|
|
210
|
+
|
|
211
|
+
// Conditional consumer via config
|
|
212
|
+
consumerChannels: isProduction ? [
|
|
213
|
+
defineRabbitConsumer({ queue: 'debug.trace', ... })
|
|
214
|
+
] : [],
|
|
215
|
+
```
|
|
231
216
|
|
|
232
|
-
|
|
233
|
-
any module that contains an consumer callback function.
|
|
217
|
+
### Handler signature
|
|
234
218
|
|
|
235
|
-
|
|
219
|
+
Every consumer handler follows the same signature regardless of registration
|
|
220
|
+
method:
|
|
236
221
|
|
|
237
222
|
```typescript
|
|
238
|
-
async
|
|
223
|
+
async handler(content: T, params?: MessageParams): Promise<void>;
|
|
239
224
|
```
|
|
240
225
|
|
|
241
|
-
|
|
226
|
+
The `MessageParams` object is optional and contains:
|
|
242
227
|
|
|
243
228
|
```typescript
|
|
244
|
-
|
|
229
|
+
type MessageParams = {
|
|
245
230
|
message: ConsumeMessage;
|
|
246
231
|
channel: ConfirmChannel;
|
|
247
232
|
queue: string;
|
|
233
|
+
originalRoutingKey?: string;
|
|
248
234
|
};
|
|
249
235
|
```
|
|
250
236
|
|
|
251
|
-
|
|
237
|
+
The `content` parameter is the parsed message body. The library automatically
|
|
238
|
+
attempts to parse the message as JSON. If parsing fails, the raw string is
|
|
239
|
+
passed instead.
|
|
252
240
|
|
|
253
|
-
You can
|
|
241
|
+
You can implement the `ConsumerHandler<T>` interface to strongly type your
|
|
242
|
+
consumer:
|
|
254
243
|
|
|
255
244
|
```typescript
|
|
256
|
-
|
|
257
|
-
type: string;
|
|
258
|
-
id: number;
|
|
259
|
-
}
|
|
245
|
+
import { ConsumerHandler, MessageParams } from '@bgaldino/nestjs-rabbitmq';
|
|
260
246
|
|
|
261
247
|
@Injectable()
|
|
262
|
-
export class
|
|
263
|
-
|
|
264
|
-
console.log(content.
|
|
248
|
+
export class OrderService implements ConsumerHandler<OrderPayload> {
|
|
249
|
+
async messageHandler(content: OrderPayload, params?: MessageParams): Promise<void> {
|
|
250
|
+
console.log(content.orderId);
|
|
265
251
|
}
|
|
266
252
|
}
|
|
267
253
|
```
|
|
268
254
|
|
|
269
|
-
###
|
|
255
|
+
### Consumer groups
|
|
270
256
|
|
|
271
|
-
|
|
257
|
+
Groups allow you to control which consumers are enabled on a given deployment.
|
|
258
|
+
This is useful when multiple instances of the same application serve different
|
|
259
|
+
roles.
|
|
272
260
|
|
|
273
261
|
```typescript
|
|
274
|
-
|
|
275
|
-
|
|
276
|
-
|
|
277
|
-
|
|
262
|
+
@RabbitConsumer({
|
|
263
|
+
queue: 'heavy.processing',
|
|
264
|
+
exchangeName: 'jobs',
|
|
265
|
+
routingKey: 'heavy.*',
|
|
266
|
+
group: 'workers',
|
|
278
267
|
})
|
|
279
|
-
|
|
268
|
+
async processHeavyJob(content: any) { ... }
|
|
269
|
+
```
|
|
270
|
+
|
|
271
|
+
The active group is determined by:
|
|
272
|
+
|
|
273
|
+
1. The `RMQ_CONSUMER_GROUP` environment variable (highest priority)
|
|
274
|
+
2. The `group` parameter passed to `createConsumers(group)`
|
|
275
|
+
3. Defaults to `"rabbit-default"`
|
|
276
|
+
|
|
277
|
+
Consumers without an explicit group are assigned to the active group and will
|
|
278
|
+
always be initialized. Consumers with a group that does not match the active
|
|
279
|
+
group are skipped.
|
|
280
|
+
|
|
281
|
+
## Publishers
|
|
282
|
+
|
|
283
|
+
### Publishing messages
|
|
284
|
+
|
|
285
|
+
Inject `RabbitMQService` and call `publish()`:
|
|
286
|
+
|
|
287
|
+
```typescript
|
|
288
|
+
import { Injectable } from '@nestjs/common';
|
|
289
|
+
import { RabbitMQService } from '@bgaldino/nestjs-rabbitmq';
|
|
280
290
|
|
|
281
|
-
//consumer.service.ts
|
|
282
291
|
@Injectable()
|
|
283
|
-
export class
|
|
284
|
-
|
|
285
|
-
|
|
292
|
+
export class OrderService {
|
|
293
|
+
constructor(private readonly rabbit: RabbitMQService) {}
|
|
294
|
+
|
|
295
|
+
async createOrder(order: Order) {
|
|
296
|
+
const published = await this.rabbit.publish('orders', 'order.created', order);
|
|
297
|
+
|
|
298
|
+
if (!published) {
|
|
299
|
+
// handle publish failure
|
|
300
|
+
}
|
|
286
301
|
}
|
|
287
302
|
}
|
|
288
303
|
```
|
|
289
304
|
|
|
290
|
-
|
|
305
|
+
The `publish()` method uses [Publisher Confirms](https://www.rabbitmq.com/docs/confirms#publisher-confirms)
|
|
306
|
+
to guarantee the message was delivered to the broker before resolving the
|
|
307
|
+
promise. Returns `true` on success, `false` on failure.
|
|
308
|
+
|
|
309
|
+
### Typed publishing
|
|
310
|
+
|
|
311
|
+
You can pass a generic type to enforce the message shape at compile time:
|
|
291
312
|
|
|
292
313
|
```typescript
|
|
293
|
-
|
|
294
|
-
|
|
295
|
-
|
|
296
|
-
|
|
297
|
-
|
|
298
|
-
) {}
|
|
299
|
-
|
|
300
|
-
createRabbitOptions(): RabbitMQModuleOptions {
|
|
301
|
-
return {
|
|
302
|
-
...
|
|
303
|
-
consumerchannels: [
|
|
304
|
-
{
|
|
305
|
-
options: {
|
|
306
|
-
queue: "myqueue",
|
|
307
|
-
exchangename: "test.exchange",
|
|
308
|
-
routingkey: "myqueue",
|
|
309
|
-
prefetch: number(process.env.rabbit_prefetch ?? 10),
|
|
310
|
-
},
|
|
311
|
-
messagehandler: this.MyService.messageHandler.bind(this.MyService),
|
|
312
|
-
},
|
|
313
|
-
];
|
|
314
|
-
}
|
|
315
|
-
}
|
|
314
|
+
await this.rabbit.publish<OrderPayload>('orders', 'order.created', {
|
|
315
|
+
orderId: '123',
|
|
316
|
+
amount: 99.90,
|
|
317
|
+
});
|
|
318
|
+
```
|
|
316
319
|
|
|
317
|
-
|
|
318
|
-
|
|
319
|
-
|
|
320
|
-
|
|
321
|
-
|
|
322
|
-
|
|
323
|
-
|
|
324
|
-
|
|
325
|
-
|
|
320
|
+
You can also pass custom publish options as a fourth argument, such as
|
|
321
|
+
additional headers or properties.
|
|
322
|
+
|
|
323
|
+
## Multi-vhost Connections
|
|
324
|
+
|
|
325
|
+
If your application needs to consume from or publish to multiple RabbitMQ
|
|
326
|
+
vhosts (or entirely different brokers), you can use named connections.
|
|
327
|
+
Each connection is a self-contained unit with its own `connectionString`,
|
|
328
|
+
`delayExchangeName`, `assertExchanges`, and `consumerChannels`.
|
|
329
|
+
|
|
330
|
+
### Named connections
|
|
331
|
+
|
|
332
|
+
Replace the flat connection fields with a `connections` array. Each entry
|
|
333
|
+
must have a unique `name`:
|
|
334
|
+
|
|
335
|
+
```typescript
|
|
336
|
+
import { RabbitMQModule, ConnectionConfig } from '@bgaldino/nestjs-rabbitmq';
|
|
337
|
+
|
|
338
|
+
RabbitMQModule.forRoot({
|
|
339
|
+
connections: [
|
|
340
|
+
{
|
|
341
|
+
name: 'default',
|
|
342
|
+
connectionString: 'amqp://localhost/main',
|
|
343
|
+
delayExchangeName: 'my_app',
|
|
344
|
+
assertExchanges: [{ name: 'orders', type: 'topic' }],
|
|
345
|
+
},
|
|
346
|
+
{
|
|
347
|
+
name: 'shared-bus',
|
|
348
|
+
connectionString: 'amqp://localhost/shared',
|
|
349
|
+
delayExchangeName: 'shared_app',
|
|
350
|
+
assertExchanges: [{ name: 'events', type: 'topic' }],
|
|
351
|
+
},
|
|
352
|
+
],
|
|
326
353
|
})
|
|
327
|
-
export class AppModule {}
|
|
328
354
|
```
|
|
329
355
|
|
|
330
|
-
|
|
356
|
+
The flat shorthand (`connectionString` at the root level) still works for
|
|
357
|
+
single-connection setups. Internally it becomes a connection named
|
|
358
|
+
`"default"`. You cannot set both `connectionString` and `connections` at the
|
|
359
|
+
same time.
|
|
360
|
+
|
|
361
|
+
### Targeting a connection from consumers
|
|
331
362
|
|
|
332
|
-
|
|
333
|
-
|
|
363
|
+
Add the `connection` field to `@RabbitConsumer()` or to a config-based
|
|
364
|
+
consumer to specify which connection it should attach to. If omitted,
|
|
365
|
+
it defaults to `"default"`:
|
|
334
366
|
|
|
335
367
|
```typescript
|
|
336
|
-
|
|
337
|
-
|
|
338
|
-
|
|
339
|
-
|
|
340
|
-
|
|
368
|
+
@RabbitConsumer({
|
|
369
|
+
queue: 'events.audit',
|
|
370
|
+
exchangeName: 'events',
|
|
371
|
+
routingKey: '#',
|
|
372
|
+
connection: 'shared-bus',
|
|
373
|
+
})
|
|
374
|
+
async auditEvents(content: any) { ... }
|
|
375
|
+
```
|
|
376
|
+
|
|
377
|
+
Config-based consumers declared inside a connection's `consumerChannels` are
|
|
378
|
+
automatically scoped to that connection:
|
|
379
|
+
|
|
380
|
+
```typescript
|
|
381
|
+
connections: [
|
|
382
|
+
{
|
|
383
|
+
name: 'default',
|
|
384
|
+
connectionString: 'amqp://localhost/main',
|
|
385
|
+
delayExchangeName: 'my_app',
|
|
386
|
+
assertExchanges: [{ name: 'orders', type: 'topic' }],
|
|
387
|
+
consumerChannels: [
|
|
388
|
+
defineRabbitConsumer({
|
|
389
|
+
queue: 'order.created',
|
|
390
|
+
exchangeName: 'orders',
|
|
391
|
+
routingKey: 'order.created',
|
|
392
|
+
handler: { provider: OrderService, methodName: 'handleOrderCreated' },
|
|
393
|
+
}),
|
|
394
|
+
],
|
|
395
|
+
},
|
|
396
|
+
],
|
|
341
397
|
```
|
|
342
398
|
|
|
343
|
-
|
|
344
|
-
|
|
399
|
+
### Publishing to a specific connection
|
|
400
|
+
|
|
401
|
+
Pass the `connection` option to `publish()`:
|
|
345
402
|
|
|
346
|
-
|
|
347
|
-
|
|
348
|
-
|
|
403
|
+
```typescript
|
|
404
|
+
await this.rabbit.publish('events', 'audit.created', payload, {
|
|
405
|
+
connection: 'shared-bus',
|
|
406
|
+
});
|
|
407
|
+
```
|
|
349
408
|
|
|
350
|
-
When the
|
|
351
|
-
message to the `.dlq` queue.
|
|
409
|
+
When omitted, the message is published to the `"default"` connection.
|
|
352
410
|
|
|
353
|
-
##
|
|
411
|
+
## Retry Strategy
|
|
354
412
|
|
|
355
|
-
Each consumer can
|
|
356
|
-
|
|
413
|
+
Each consumer can define a `retryStrategy` to handle transient failures. When
|
|
414
|
+
the handler throws an error, the message is published to a retry queue
|
|
415
|
+
(`{queue}.retry`) with a TTL. Once the TTL expires, the message is routed
|
|
416
|
+
back to the original queue for another attempt.
|
|
357
417
|
|
|
358
418
|
```typescript
|
|
359
|
-
|
|
360
|
-
|
|
361
|
-
|
|
362
|
-
|
|
419
|
+
@RabbitConsumer({
|
|
420
|
+
queue: 'order.process',
|
|
421
|
+
exchangeName: 'orders',
|
|
422
|
+
routingKey: 'order.process',
|
|
423
|
+
retryStrategy: {
|
|
424
|
+
enabled: true,
|
|
425
|
+
maxAttempts: 5,
|
|
426
|
+
delay: (content, attempt, error) => attempt * 5000,
|
|
427
|
+
},
|
|
428
|
+
})
|
|
429
|
+
async processOrder(content: OrderPayload) { ... }
|
|
363
430
|
```
|
|
364
431
|
|
|
365
|
-
|
|
432
|
+
The `delay` callback receives the message content, the current attempt number,
|
|
433
|
+
and the error that was thrown. It should return the delay in milliseconds
|
|
434
|
+
before the next retry. The return value controls the behavior:
|
|
366
435
|
|
|
367
|
-
|
|
368
|
-
|
|
369
|
-
|
|
436
|
+
- A positive number: the message is sent to the retry queue with that TTL
|
|
437
|
+
- Zero: the message is retried immediately (republished to the end of the
|
|
438
|
+
original queue)
|
|
439
|
+
- A negative number: retrying is skipped entirely, and the message goes
|
|
440
|
+
straight to the dead letter strategy
|
|
370
441
|
|
|
371
|
-
|
|
372
|
-
it will behave differently, such as:
|
|
442
|
+
**Defaults** (when `retryStrategy` is not specified):
|
|
373
443
|
|
|
374
|
-
-
|
|
375
|
-
|
|
376
|
-
-
|
|
377
|
-
it entirely**, allowing you to drop messages if the callback execution is
|
|
378
|
-
successful
|
|
444
|
+
- `enabled`: true
|
|
445
|
+
- `maxAttempts`: 5
|
|
446
|
+
- `delay`: () => 5000
|
|
379
447
|
|
|
380
|
-
|
|
381
|
-
|
|
382
|
-
forwarded to the DLQ normally.
|
|
448
|
+
When the maximum number of attempts is reached, the message is nacked and sent
|
|
449
|
+
to the dead letter queue.
|
|
383
450
|
|
|
384
|
-
##
|
|
451
|
+
## Dead Letter Strategy
|
|
385
452
|
|
|
386
|
-
|
|
387
|
-
|
|
453
|
+
Each consumer can define a `deadLetterStrategy` to control what happens when
|
|
454
|
+
a message exhausts all retry attempts:
|
|
388
455
|
|
|
389
456
|
```typescript
|
|
390
|
-
|
|
391
|
-
|
|
392
|
-
|
|
393
|
-
|
|
394
|
-
|
|
395
|
-
|
|
457
|
+
@RabbitConsumer({
|
|
458
|
+
queue: 'order.process',
|
|
459
|
+
exchangeName: 'orders',
|
|
460
|
+
routingKey: 'order.process',
|
|
461
|
+
deadLetterStrategy: {
|
|
462
|
+
suffix: '.dlq',
|
|
463
|
+
callback: async (content) => {
|
|
464
|
+
await alertService.notify('Order processing failed', content);
|
|
465
|
+
return true;
|
|
466
|
+
},
|
|
467
|
+
},
|
|
468
|
+
})
|
|
469
|
+
async processOrder(content: OrderPayload) { ... }
|
|
396
470
|
```
|
|
397
471
|
|
|
398
|
-
|
|
472
|
+
The `suffix` controls the name of the dead letter queue. Defaults to `.dlq`,
|
|
473
|
+
resulting in a queue named `{queue}.dlq`.
|
|
474
|
+
|
|
475
|
+
The `callback` is executed before sending the message to the DLQ. It receives
|
|
476
|
+
the raw message content and should return a boolean:
|
|
477
|
+
|
|
478
|
+
- `true`: the message is forwarded to the DLQ after the callback executes
|
|
479
|
+
- `false`: the message is acknowledged and dropped, it will not go to the DLQ
|
|
480
|
+
|
|
481
|
+
If the callback throws an error, the message is forwarded to the DLQ regardless.
|
|
482
|
+
|
|
483
|
+
## Disabling the automatic ack
|
|
484
|
+
|
|
485
|
+
By default, the consumer automatically acknowledges the message after the
|
|
486
|
+
handler completes. If you need manual control over acknowledgement, disable it:
|
|
399
487
|
|
|
400
488
|
```typescript
|
|
401
|
-
|
|
402
|
-
|
|
403
|
-
|
|
404
|
-
|
|
405
|
-
|
|
489
|
+
@RabbitConsumer({
|
|
490
|
+
queue: 'order.process',
|
|
491
|
+
exchangeName: 'orders',
|
|
492
|
+
routingKey: 'order.process',
|
|
493
|
+
autoAck: false,
|
|
494
|
+
})
|
|
495
|
+
async processOrder(content: OrderPayload, params: MessageParams) {
|
|
496
|
+
// do work
|
|
497
|
+
params.channel.ack(params.message);
|
|
406
498
|
}
|
|
407
499
|
```
|
|
408
500
|
|
|
409
|
-
|
|
501
|
+
When `autoAck` is disabled, you are responsible for calling `channel.ack()` or
|
|
502
|
+
`channel.nack()`. If you do not acknowledge the message, it will remain
|
|
503
|
+
unacknowledged and RabbitMQ will redeliver it when the consumer disconnects.
|
|
504
|
+
|
|
505
|
+
## Custom Header Metadata
|
|
410
506
|
|
|
411
|
-
Every published message
|
|
507
|
+
Every published message includes the following custom headers automatically:
|
|
412
508
|
|
|
413
509
|
```json
|
|
414
510
|
{
|
|
415
|
-
"x-
|
|
416
|
-
|
|
417
|
-
|
|
418
|
-
"published-at": ISODate
|
|
419
|
-
}
|
|
511
|
+
"x-original-exchange": "exchange_name",
|
|
512
|
+
"x-original-routing-key": "routing.key",
|
|
513
|
+
"x-published-at": "2026-01-01T00:00:00.000Z"
|
|
420
514
|
}
|
|
421
515
|
```
|
|
422
516
|
|
|
423
|
-
|
|
424
|
-
|
|
517
|
+
These headers preserve the original exchange and routing key references, which
|
|
518
|
+
would otherwise be lost when a message is routed through retry queues or the
|
|
519
|
+
DLQ.
|
|
520
|
+
|
|
521
|
+
The `originalRoutingKey` field in `MessageParams` is derived from these headers
|
|
522
|
+
when available, falling back to the message's current routing key.
|
|
425
523
|
|
|
426
|
-
## Extra
|
|
524
|
+
## Extra Options
|
|
427
525
|
|
|
428
526
|
### Consumer manual loading
|
|
429
527
|
|
|
430
|
-
|
|
431
|
-
|
|
432
|
-
|
|
528
|
+
Consumers are attached during the `OnApplicationBootstrap` lifecycle, which
|
|
529
|
+
means the application begins receiving messages as soon as all modules are
|
|
530
|
+
initialized, but before `app.listen()` resolves.
|
|
433
531
|
|
|
434
|
-
If
|
|
435
|
-
|
|
436
|
-
|
|
437
|
-
call the consumer instantiation.
|
|
532
|
+
If you need consumers to start only after the HTTP server is ready (or need
|
|
533
|
+
to defer startup for any other reason), set `consumerManualLoad: true` and
|
|
534
|
+
call the initialization manually:
|
|
438
535
|
|
|
439
|
-
|
|
536
|
+
```typescript
|
|
537
|
+
RabbitMQModule.forRoot({
|
|
538
|
+
connectionString: 'amqp://localhost',
|
|
539
|
+
delayExchangeName: 'my_app',
|
|
540
|
+
assertExchanges: [],
|
|
541
|
+
extraOptions: {
|
|
542
|
+
consumerManualLoad: true,
|
|
543
|
+
},
|
|
544
|
+
})
|
|
545
|
+
```
|
|
440
546
|
|
|
441
547
|
```typescript
|
|
442
548
|
async function bootstrap() {
|
|
443
549
|
const app = await NestFactory.create(AppModule);
|
|
444
550
|
await app.listen(3000);
|
|
445
551
|
|
|
446
|
-
const rabbit
|
|
447
|
-
await rabbit.
|
|
552
|
+
const rabbit = app.get(RabbitMQService);
|
|
553
|
+
await rabbit.startConsumers();
|
|
448
554
|
}
|
|
449
555
|
bootstrap();
|
|
450
556
|
```
|
|
451
557
|
|
|
558
|
+
You can also pass a group name to `startConsumers(group)` to initialize only consumers
|
|
559
|
+
belonging to that group.
|
|
560
|
+
|
|
452
561
|
### Message inspection and logging
|
|
453
562
|
|
|
454
|
-
You can inspect
|
|
455
|
-
|
|
456
|
-
to either: `all | consumer | publisher | none`.
|
|
563
|
+
You can inspect consumer and publisher messages by setting `extraOptions.logType`
|
|
564
|
+
or the `RABBITMQ_LOG_TYPE` environment variable to one of:
|
|
457
565
|
|
|
458
|
-
|
|
566
|
+
- `all` — logs both consumer and publisher messages
|
|
567
|
+
- `consumer` — logs consumer messages only
|
|
568
|
+
- `publisher` — logs publisher messages only
|
|
569
|
+
- `none` — no message logging (default)
|
|
459
570
|
|
|
460
|
-
|
|
461
|
-
as long as it follows the Logger/Console interfaces. The SDK will use the given
|
|
462
|
-
instance to log any messages
|
|
571
|
+
The environment variable takes precedence over the config value.
|
|
463
572
|
|
|
464
|
-
|
|
573
|
+
Consumer errors are always logged regardless of this setting.
|
|
465
574
|
|
|
466
|
-
|
|
575
|
+
### Health check
|
|
467
576
|
|
|
468
|
-
|
|
469
|
-
|
|
470
|
-
pnpm build
|
|
471
|
-
```
|
|
577
|
+
You can check the connection status of both the consumer and publisher
|
|
578
|
+
connections:
|
|
472
579
|
|
|
473
|
-
|
|
580
|
+
```typescript
|
|
581
|
+
const rabbit = app.get(RabbitMQService);
|
|
474
582
|
|
|
475
|
-
|
|
583
|
+
// Check all connections (returns 0 if any connection is offline)
|
|
584
|
+
const status = rabbit.checkHealth(); // 1 = online, 0 = offline
|
|
476
585
|
|
|
477
|
-
|
|
478
|
-
|
|
479
|
-
|
|
480
|
-
- [ ] Make the publisher method strongly typed based on the `assertExchanges`
|
|
481
|
-
`exchangeName` and `routingKeys` configurations
|
|
586
|
+
// Check a specific connection
|
|
587
|
+
const sharedStatus = rabbit.checkHealth('shared-bus');
|
|
588
|
+
```
|
|
482
589
|
|
|
483
|
-
##
|
|
590
|
+
## Building locally
|
|
484
591
|
|
|
485
|
-
|
|
592
|
+
```shell
|
|
593
|
+
pnpm install
|
|
594
|
+
pnpm build
|
|
595
|
+
```
|
|
486
596
|
|
|
487
597
|
## License
|
|
488
598
|
|
|
489
|
-
[MIT License](https://github.com/
|
|
599
|
+
[MIT License](https://github.com/brunogaldino/nestjs-rabbitmq/blob/master/LICENSE)
|