0
# Module Integration
1
2
NestJS module integration for dependency injection and configuration management of microservice clients and servers, providing seamless integration with the NestJS dependency injection system and modular architecture.
3
4
## Capabilities
5
6
### Clients Module
7
8
Module for registering and managing microservice client instances with dependency injection support.
9
10
```typescript { .api }
11
/**
12
* Module for microservice client registration and dependency injection
13
*/
14
class ClientsModule {
15
/**
16
* Register clients synchronously with static configuration
17
* @param options - Array of client configuration options
18
* @returns Dynamic module with registered clients
19
*/
20
static register(options: ClientsModuleOptions): DynamicModule;
21
22
/**
23
* Register clients asynchronously with dynamic configuration
24
* @param options - Async configuration options
25
* @returns Dynamic module with registered clients
26
*/
27
static registerAsync(options: ClientsModuleAsyncOptions): DynamicModule;
28
}
29
30
/**
31
* Client configuration options for multiple clients
32
*/
33
interface ClientsModuleOptions extends Array<ClientsModuleOption> {}
34
35
interface ClientsModuleOption {
36
/** Unique name for the client */
37
name: string | symbol;
38
/** Transport configuration */
39
transport: Transport;
40
/** Transport-specific options */
41
options?: any;
42
}
43
44
/**
45
* Async configuration options for clients module
46
*/
47
interface ClientsModuleAsyncOptions {
48
/** Module imports for dependencies */
49
imports?: any[];
50
/** Factory function for creating client options */
51
useFactory?: (...args: any[]) => ClientsModuleOptions | Promise<ClientsModuleOptions>;
52
/** Dependencies to inject into useFactory */
53
inject?: any[];
54
/** Use existing provider for configuration */
55
useExisting?: any;
56
/** Use class provider for configuration */
57
useClass?: any;
58
}
59
```
60
61
**Usage Examples:**
62
63
```typescript
64
import { Module } from '@nestjs/common';
65
import { ClientsModule, Transport } from '@nestjs/microservices';
66
67
// Static registration
68
@Module({
69
imports: [
70
ClientsModule.register([
71
{
72
name: 'USER_SERVICE',
73
transport: Transport.TCP,
74
options: {
75
host: '127.0.0.1',
76
port: 3001,
77
},
78
},
79
{
80
name: 'NOTIFICATION_SERVICE',
81
transport: Transport.REDIS,
82
options: {
83
host: 'localhost',
84
port: 6379,
85
},
86
},
87
{
88
name: 'ANALYTICS_SERVICE',
89
transport: Transport.KAFKA,
90
options: {
91
client: {
92
clientId: 'analytics-client',
93
brokers: ['localhost:9092'],
94
},
95
},
96
},
97
]),
98
],
99
providers: [AppService],
100
controllers: [AppController],
101
})
102
export class AppModule {}
103
104
// Async registration with configuration service
105
@Module({
106
imports: [
107
ConfigModule,
108
ClientsModule.registerAsync({
109
imports: [ConfigModule],
110
useFactory: async (configService: ConfigService) => [
111
{
112
name: 'USER_SERVICE',
113
transport: Transport.TCP,
114
options: {
115
host: configService.get('USER_SERVICE_HOST'),
116
port: configService.get('USER_SERVICE_PORT'),
117
},
118
},
119
{
120
name: 'NOTIFICATION_SERVICE',
121
transport: Transport.REDIS,
122
options: {
123
host: configService.get('REDIS_HOST'),
124
port: configService.get('REDIS_PORT'),
125
password: configService.get('REDIS_PASSWORD'),
126
},
127
},
128
],
129
inject: [ConfigService],
130
}),
131
],
132
})
133
export class AppModule {}
134
```
135
136
### Client Decorator
137
138
Property decorator for injecting microservice client instances into classes.
139
140
```typescript { .api }
141
/**
142
* Property decorator for injecting ClientProxy instances
143
* @param metadata - Client configuration or token name
144
* @returns Property decorator
145
*/
146
function Client(metadata?: ClientOptions | string | symbol): PropertyDecorator;
147
```
148
149
**Usage Examples:**
150
151
```typescript
152
import { Injectable } from '@nestjs/common';
153
import { Client, ClientProxy, Transport } from '@nestjs/microservices';
154
155
@Injectable()
156
export class AppService {
157
// Direct client configuration
158
@Client({
159
transport: Transport.TCP,
160
options: {
161
host: '127.0.0.1',
162
port: 3001,
163
},
164
})
165
private userClient: ClientProxy;
166
167
// Using registered client by name
168
@Client('NOTIFICATION_SERVICE')
169
private notificationClient: ClientProxy;
170
171
@Client('ANALYTICS_SERVICE')
172
private analyticsClient: ClientProxy;
173
174
async getUser(id: number): Promise<any> {
175
return this.userClient.send({ cmd: 'get_user' }, { id }).toPromise();
176
}
177
178
async sendNotification(notification: any): Promise<void> {
179
this.notificationClient.emit('send_notification', notification);
180
}
181
182
async trackEvent(event: any): Promise<void> {
183
this.analyticsClient.emit('track_event', event);
184
}
185
186
// Lifecycle management
187
async onModuleInit() {
188
await this.userClient.connect();
189
await this.notificationClient.connect();
190
await this.analyticsClient.connect();
191
}
192
193
async onModuleDestroy() {
194
await this.userClient.close();
195
await this.notificationClient.close();
196
await this.analyticsClient.close();
197
}
198
}
199
```
200
201
### Service Integration Patterns
202
203
Advanced patterns for integrating microservice clients with NestJS services.
204
205
**Service with Multiple Clients:**
206
207
```typescript
208
import { Injectable, Inject, OnModuleInit, OnModuleDestroy } from '@nestjs/common';
209
import { ClientProxy } from '@nestjs/microservices';
210
211
@Injectable()
212
export class OrderService implements OnModuleInit, OnModuleDestroy {
213
constructor(
214
@Inject('USER_SERVICE') private userClient: ClientProxy,
215
@Inject('INVENTORY_SERVICE') private inventoryClient: ClientProxy,
216
@Inject('PAYMENT_SERVICE') private paymentClient: ClientProxy,
217
@Inject('NOTIFICATION_SERVICE') private notificationClient: ClientProxy,
218
) {}
219
220
async onModuleInit() {
221
// Connect all clients
222
await Promise.all([
223
this.userClient.connect(),
224
this.inventoryClient.connect(),
225
this.paymentClient.connect(),
226
this.notificationClient.connect(),
227
]);
228
}
229
230
async onModuleDestroy() {
231
// Close all clients
232
await Promise.all([
233
this.userClient.close(),
234
this.inventoryClient.close(),
235
this.paymentClient.close(),
236
this.notificationClient.close(),
237
]);
238
}
239
240
async processOrder(orderData: CreateOrderDto): Promise<Order> {
241
try {
242
// Validate user
243
const user = await this.userClient
244
.send({ cmd: 'get_user' }, { id: orderData.userId })
245
.toPromise();
246
247
if (!user) {
248
throw new Error('User not found');
249
}
250
251
// Check inventory
252
const inventoryCheck = await this.inventoryClient
253
.send({ cmd: 'check_availability' }, { items: orderData.items })
254
.toPromise();
255
256
if (!inventoryCheck.available) {
257
throw new Error('Items not available');
258
}
259
260
// Process payment
261
const payment = await this.paymentClient
262
.send({ cmd: 'process_payment' }, {
263
userId: orderData.userId,
264
amount: orderData.total,
265
paymentMethod: orderData.paymentMethod,
266
})
267
.toPromise();
268
269
if (!payment.success) {
270
throw new Error('Payment failed');
271
}
272
273
// Reserve inventory
274
await this.inventoryClient
275
.send({ cmd: 'reserve_items' }, { items: orderData.items })
276
.toPromise();
277
278
// Create order
279
const order = await this.createOrder(orderData, payment.id);
280
281
// Send confirmation notification (fire-and-forget)
282
this.notificationClient.emit('order_created', {
283
orderId: order.id,
284
userId: user.id,
285
email: user.email,
286
items: orderData.items,
287
});
288
289
return order;
290
} catch (error) {
291
// Handle rollback if needed
292
await this.handleOrderFailure(orderData, error);
293
throw error;
294
}
295
}
296
297
private async createOrder(orderData: CreateOrderDto, paymentId: string): Promise<Order> {
298
// Local order creation logic
299
return this.orderRepository.create({
300
...orderData,
301
paymentId,
302
status: 'confirmed',
303
createdAt: new Date(),
304
});
305
}
306
307
private async handleOrderFailure(orderData: CreateOrderDto, error: Error): Promise<void> {
308
// Emit failure event for cleanup
309
this.notificationClient.emit('order_failed', {
310
userId: orderData.userId,
311
error: error.message,
312
timestamp: new Date().toISOString(),
313
});
314
}
315
}
316
```
317
318
**Client Health Monitoring:**
319
320
```typescript
321
import { Injectable, Logger } from '@nestjs/common';
322
import { ClientProxy } from '@nestjs/microservices';
323
import { Cron, CronExpression } from '@nestjs/schedule';
324
325
@Injectable()
326
export class ClientHealthService {
327
private readonly logger = new Logger(ClientHealthService.name);
328
329
constructor(
330
@Inject('USER_SERVICE') private userClient: ClientProxy,
331
@Inject('INVENTORY_SERVICE') private inventoryClient: ClientProxy,
332
) {}
333
334
@Cron(CronExpression.EVERY_30_SECONDS)
335
async checkClientHealth() {
336
const clients = [
337
{ name: 'USER_SERVICE', client: this.userClient },
338
{ name: 'INVENTORY_SERVICE', client: this.inventoryClient },
339
];
340
341
for (const { name, client } of clients) {
342
try {
343
await client.send({ cmd: 'health_check' }, {}).toPromise();
344
this.logger.log(`${name} is healthy`);
345
} catch (error) {
346
this.logger.error(`${name} health check failed:`, error.message);
347
348
// Attempt reconnection
349
try {
350
await client.close();
351
await client.connect();
352
this.logger.log(`${name} reconnected successfully`);
353
} catch (reconnectError) {
354
this.logger.error(`${name} reconnection failed:`, reconnectError.message);
355
}
356
}
357
}
358
}
359
}
360
```
361
362
**Configuration-Based Client Factory:**
363
364
```typescript
365
import { Injectable } from '@nestjs/common';
366
import { ConfigService } from '@nestjs/config';
367
import { ClientProxy, ClientProxyFactory, Transport } from '@nestjs/microservices';
368
369
interface ServiceConfig {
370
name: string;
371
transport: Transport;
372
options: any;
373
enabled: boolean;
374
}
375
376
@Injectable()
377
export class ClientFactory {
378
private clients: Map<string, ClientProxy> = new Map();
379
380
constructor(private configService: ConfigService) {}
381
382
async createClients(): Promise<void> {
383
const services = this.configService.get<ServiceConfig[]>('microservices');
384
385
for (const service of services) {
386
if (service.enabled) {
387
const client = ClientProxyFactory.create({
388
transport: service.transport,
389
options: service.options,
390
});
391
392
await client.connect();
393
this.clients.set(service.name, client);
394
}
395
}
396
}
397
398
getClient(name: string): ClientProxy {
399
const client = this.clients.get(name);
400
if (!client) {
401
throw new Error(`Client ${name} not found or not enabled`);
402
}
403
return client;
404
}
405
406
async closeAll(): Promise<void> {
407
const closePromises = Array.from(this.clients.values()).map(client => client.close());
408
await Promise.all(closePromises);
409
this.clients.clear();
410
}
411
}
412
413
// Usage with configuration
414
// config/microservices.config.ts
415
export default () => ({
416
microservices: [
417
{
418
name: 'USER_SERVICE',
419
transport: Transport.TCP,
420
options: {
421
host: process.env.USER_SERVICE_HOST || '127.0.0.1',
422
port: parseInt(process.env.USER_SERVICE_PORT) || 3001,
423
},
424
enabled: process.env.USER_SERVICE_ENABLED === 'true',
425
},
426
{
427
name: 'NOTIFICATION_SERVICE',
428
transport: Transport.REDIS,
429
options: {
430
host: process.env.REDIS_HOST || 'localhost',
431
port: parseInt(process.env.REDIS_PORT) || 6379,
432
},
433
enabled: process.env.NOTIFICATION_SERVICE_ENABLED === 'true',
434
},
435
],
436
});
437
```
438
439
### Testing Integration
440
441
Patterns for testing services with microservice clients.
442
443
```typescript
444
import { Test, TestingModule } from '@nestjs/testing';
445
import { ClientsModule, ClientProxy } from '@nestjs/microservices';
446
import { of } from 'rxjs';
447
448
describe('OrderService', () => {
449
let service: OrderService;
450
let userClient: ClientProxy;
451
let inventoryClient: ClientProxy;
452
453
beforeEach(async () => {
454
const mockUserClient = {
455
send: jest.fn(),
456
emit: jest.fn(),
457
connect: jest.fn().mockResolvedValue(undefined),
458
close: jest.fn().mockResolvedValue(undefined),
459
};
460
461
const mockInventoryClient = {
462
send: jest.fn(),
463
emit: jest.fn(),
464
connect: jest.fn().mockResolvedValue(undefined),
465
close: jest.fn().mockResolvedValue(undefined),
466
};
467
468
const module: TestingModule = await Test.createTestingModule({
469
providers: [
470
OrderService,
471
{
472
provide: 'USER_SERVICE',
473
useValue: mockUserClient,
474
},
475
{
476
provide: 'INVENTORY_SERVICE',
477
useValue: mockInventoryClient,
478
},
479
],
480
}).compile();
481
482
service = module.get<OrderService>(OrderService);
483
userClient = module.get<ClientProxy>('USER_SERVICE');
484
inventoryClient = module.get<ClientProxy>('INVENTORY_SERVICE');
485
});
486
487
it('should process order successfully', async () => {
488
// Setup mocks
489
(userClient.send as jest.Mock).mockReturnValue(
490
of({ id: 1, email: 'test@example.com' })
491
);
492
(inventoryClient.send as jest.Mock).mockReturnValue(
493
of({ available: true })
494
);
495
496
const orderData = {
497
userId: 1,
498
items: [{ id: 1, quantity: 2 }],
499
total: 100,
500
};
501
502
const result = await service.processOrder(orderData);
503
504
expect(result).toBeDefined();
505
expect(userClient.send).toHaveBeenCalledWith(
506
{ cmd: 'get_user' },
507
{ id: 1 }
508
);
509
expect(inventoryClient.send).toHaveBeenCalledWith(
510
{ cmd: 'check_availability' },
511
{ items: orderData.items }
512
);
513
});
514
});
515
```