CtrlK
BlogDocsLog inGet started
Tessl Logo

tessl/npm-nestjs--microservices

Nest microservices framework providing scalable distributed systems with multiple transport layers and communication patterns.

Pending
Quality

Pending

Does it follow best practices?

Impact

Pending

No eval scenarios have been run

SecuritybySnyk

Pending

The risk profile of this skill

Overview
Eval results
Files

modules.mddocs/

Module Integration

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.

Capabilities

Clients Module

Module for registering and managing microservice client instances with dependency injection support.

/**
 * Module for microservice client registration and dependency injection
 */
class ClientsModule {
  /**
   * Register clients synchronously with static configuration
   * @param options - Array of client configuration options
   * @returns Dynamic module with registered clients
   */
  static register(options: ClientsModuleOptions): DynamicModule;
  
  /**
   * Register clients asynchronously with dynamic configuration
   * @param options - Async configuration options
   * @returns Dynamic module with registered clients
   */
  static registerAsync(options: ClientsModuleAsyncOptions): DynamicModule;
}

/**
 * Client configuration options for multiple clients
 */
interface ClientsModuleOptions extends Array<ClientsModuleOption> {}

interface ClientsModuleOption {
  /** Unique name for the client */
  name: string | symbol;
  /** Transport configuration */
  transport: Transport;
  /** Transport-specific options */
  options?: any;
}

/**
 * Async configuration options for clients module
 */
interface ClientsModuleAsyncOptions {
  /** Module imports for dependencies */
  imports?: any[];
  /** Factory function for creating client options */
  useFactory?: (...args: any[]) => ClientsModuleOptions | Promise<ClientsModuleOptions>;
  /** Dependencies to inject into useFactory */
  inject?: any[];
  /** Use existing provider for configuration */
  useExisting?: any;
  /** Use class provider for configuration */
  useClass?: any;
}

Usage Examples:

import { Module } from '@nestjs/common';
import { ClientsModule, Transport } from '@nestjs/microservices';

// Static registration
@Module({
  imports: [
    ClientsModule.register([
      {
        name: 'USER_SERVICE',
        transport: Transport.TCP,
        options: {
          host: '127.0.0.1',
          port: 3001,
        },
      },
      {
        name: 'NOTIFICATION_SERVICE',
        transport: Transport.REDIS,
        options: {
          host: 'localhost',
          port: 6379,
        },
      },
      {
        name: 'ANALYTICS_SERVICE',
        transport: Transport.KAFKA,
        options: {
          client: {
            clientId: 'analytics-client',
            brokers: ['localhost:9092'],
          },
        },
      },
    ]),
  ],
  providers: [AppService],
  controllers: [AppController],
})
export class AppModule {}

// Async registration with configuration service
@Module({
  imports: [
    ConfigModule,
    ClientsModule.registerAsync({
      imports: [ConfigModule],
      useFactory: async (configService: ConfigService) => [
        {
          name: 'USER_SERVICE',
          transport: Transport.TCP,
          options: {
            host: configService.get('USER_SERVICE_HOST'),
            port: configService.get('USER_SERVICE_PORT'),
          },
        },
        {
          name: 'NOTIFICATION_SERVICE',
          transport: Transport.REDIS,
          options: {
            host: configService.get('REDIS_HOST'),
            port: configService.get('REDIS_PORT'),
            password: configService.get('REDIS_PASSWORD'),
          },
        },
      ],
      inject: [ConfigService],
    }),
  ],
})
export class AppModule {}

Client Decorator

Property decorator for injecting microservice client instances into classes.

/**
 * Property decorator for injecting ClientProxy instances
 * @param metadata - Client configuration or token name
 * @returns Property decorator
 */
function Client(metadata?: ClientOptions | string | symbol): PropertyDecorator;

Usage Examples:

import { Injectable } from '@nestjs/common';
import { Client, ClientProxy, Transport } from '@nestjs/microservices';

@Injectable()
export class AppService {
  // Direct client configuration
  @Client({
    transport: Transport.TCP,
    options: {
      host: '127.0.0.1',
      port: 3001,
    },
  })
  private userClient: ClientProxy;

  // Using registered client by name
  @Client('NOTIFICATION_SERVICE')
  private notificationClient: ClientProxy;

  @Client('ANALYTICS_SERVICE')
  private analyticsClient: ClientProxy;

  async getUser(id: number): Promise<any> {
    return this.userClient.send({ cmd: 'get_user' }, { id }).toPromise();
  }

  async sendNotification(notification: any): Promise<void> {
    this.notificationClient.emit('send_notification', notification);
  }

  async trackEvent(event: any): Promise<void> {
    this.analyticsClient.emit('track_event', event);
  }

  // Lifecycle management
  async onModuleInit() {
    await this.userClient.connect();
    await this.notificationClient.connect();
    await this.analyticsClient.connect();
  }

  async onModuleDestroy() {
    await this.userClient.close();
    await this.notificationClient.close();
    await this.analyticsClient.close();
  }
}

Service Integration Patterns

Advanced patterns for integrating microservice clients with NestJS services.

Service with Multiple Clients:

import { Injectable, Inject, OnModuleInit, OnModuleDestroy } from '@nestjs/common';
import { ClientProxy } from '@nestjs/microservices';

@Injectable()
export class OrderService implements OnModuleInit, OnModuleDestroy {
  constructor(
    @Inject('USER_SERVICE') private userClient: ClientProxy,
    @Inject('INVENTORY_SERVICE') private inventoryClient: ClientProxy,
    @Inject('PAYMENT_SERVICE') private paymentClient: ClientProxy,
    @Inject('NOTIFICATION_SERVICE') private notificationClient: ClientProxy,
  ) {}

  async onModuleInit() {
    // Connect all clients
    await Promise.all([
      this.userClient.connect(),
      this.inventoryClient.connect(),
      this.paymentClient.connect(),
      this.notificationClient.connect(),
    ]);
  }

  async onModuleDestroy() {
    // Close all clients
    await Promise.all([
      this.userClient.close(),
      this.inventoryClient.close(),
      this.paymentClient.close(),
      this.notificationClient.close(),
    ]);
  }

  async processOrder(orderData: CreateOrderDto): Promise<Order> {
    try {
      // Validate user
      const user = await this.userClient
        .send({ cmd: 'get_user' }, { id: orderData.userId })
        .toPromise();

      if (!user) {
        throw new Error('User not found');
      }

      // Check inventory
      const inventoryCheck = await this.inventoryClient
        .send({ cmd: 'check_availability' }, { items: orderData.items })
        .toPromise();

      if (!inventoryCheck.available) {
        throw new Error('Items not available');
      }

      // Process payment
      const payment = await this.paymentClient
        .send({ cmd: 'process_payment' }, {
          userId: orderData.userId,
          amount: orderData.total,
          paymentMethod: orderData.paymentMethod,
        })
        .toPromise();

      if (!payment.success) {
        throw new Error('Payment failed');
      }

      // Reserve inventory
      await this.inventoryClient
        .send({ cmd: 'reserve_items' }, { items: orderData.items })
        .toPromise();

      // Create order
      const order = await this.createOrder(orderData, payment.id);

      // Send confirmation notification (fire-and-forget)
      this.notificationClient.emit('order_created', {
        orderId: order.id,
        userId: user.id,
        email: user.email,
        items: orderData.items,
      });

      return order;
    } catch (error) {
      // Handle rollback if needed
      await this.handleOrderFailure(orderData, error);
      throw error;
    }
  }

  private async createOrder(orderData: CreateOrderDto, paymentId: string): Promise<Order> {
    // Local order creation logic
    return this.orderRepository.create({
      ...orderData,
      paymentId,
      status: 'confirmed',
      createdAt: new Date(),
    });
  }

  private async handleOrderFailure(orderData: CreateOrderDto, error: Error): Promise<void> {
    // Emit failure event for cleanup
    this.notificationClient.emit('order_failed', {
      userId: orderData.userId,
      error: error.message,
      timestamp: new Date().toISOString(),
    });
  }
}

Client Health Monitoring:

import { Injectable, Logger } from '@nestjs/common';
import { ClientProxy } from '@nestjs/microservices';
import { Cron, CronExpression } from '@nestjs/schedule';

@Injectable()
export class ClientHealthService {
  private readonly logger = new Logger(ClientHealthService.name);

  constructor(
    @Inject('USER_SERVICE') private userClient: ClientProxy,
    @Inject('INVENTORY_SERVICE') private inventoryClient: ClientProxy,
  ) {}

  @Cron(CronExpression.EVERY_30_SECONDS)
  async checkClientHealth() {
    const clients = [
      { name: 'USER_SERVICE', client: this.userClient },
      { name: 'INVENTORY_SERVICE', client: this.inventoryClient },
    ];

    for (const { name, client } of clients) {
      try {
        await client.send({ cmd: 'health_check' }, {}).toPromise();
        this.logger.log(`${name} is healthy`);
      } catch (error) {
        this.logger.error(`${name} health check failed:`, error.message);
        
        // Attempt reconnection
        try {
          await client.close();
          await client.connect();
          this.logger.log(`${name} reconnected successfully`);
        } catch (reconnectError) {
          this.logger.error(`${name} reconnection failed:`, reconnectError.message);
        }
      }
    }
  }
}

Configuration-Based Client Factory:

import { Injectable } from '@nestjs/common';
import { ConfigService } from '@nestjs/config';
import { ClientProxy, ClientProxyFactory, Transport } from '@nestjs/microservices';

interface ServiceConfig {
  name: string;
  transport: Transport;
  options: any;
  enabled: boolean;
}

@Injectable()
export class ClientFactory {
  private clients: Map<string, ClientProxy> = new Map();

  constructor(private configService: ConfigService) {}

  async createClients(): Promise<void> {
    const services = this.configService.get<ServiceConfig[]>('microservices');

    for (const service of services) {
      if (service.enabled) {
        const client = ClientProxyFactory.create({
          transport: service.transport,
          options: service.options,
        });

        await client.connect();
        this.clients.set(service.name, client);
      }
    }
  }

  getClient(name: string): ClientProxy {
    const client = this.clients.get(name);
    if (!client) {
      throw new Error(`Client ${name} not found or not enabled`);
    }
    return client;
  }

  async closeAll(): Promise<void> {
    const closePromises = Array.from(this.clients.values()).map(client => client.close());
    await Promise.all(closePromises);
    this.clients.clear();
  }
}

// Usage with configuration
// config/microservices.config.ts
export default () => ({
  microservices: [
    {
      name: 'USER_SERVICE',
      transport: Transport.TCP,
      options: {
        host: process.env.USER_SERVICE_HOST || '127.0.0.1',
        port: parseInt(process.env.USER_SERVICE_PORT) || 3001,
      },
      enabled: process.env.USER_SERVICE_ENABLED === 'true',
    },
    {
      name: 'NOTIFICATION_SERVICE',
      transport: Transport.REDIS,
      options: {
        host: process.env.REDIS_HOST || 'localhost',
        port: parseInt(process.env.REDIS_PORT) || 6379,
      },
      enabled: process.env.NOTIFICATION_SERVICE_ENABLED === 'true',
    },
  ],
});

Testing Integration

Patterns for testing services with microservice clients.

import { Test, TestingModule } from '@nestjs/testing';
import { ClientsModule, ClientProxy } from '@nestjs/microservices';
import { of } from 'rxjs';

describe('OrderService', () => {
  let service: OrderService;
  let userClient: ClientProxy;
  let inventoryClient: ClientProxy;

  beforeEach(async () => {
    const mockUserClient = {
      send: jest.fn(),
      emit: jest.fn(),
      connect: jest.fn().mockResolvedValue(undefined),
      close: jest.fn().mockResolvedValue(undefined),
    };

    const mockInventoryClient = {
      send: jest.fn(),
      emit: jest.fn(),
      connect: jest.fn().mockResolvedValue(undefined),
      close: jest.fn().mockResolvedValue(undefined),
    };

    const module: TestingModule = await Test.createTestingModule({
      providers: [
        OrderService,
        {
          provide: 'USER_SERVICE',
          useValue: mockUserClient,
        },
        {
          provide: 'INVENTORY_SERVICE',
          useValue: mockInventoryClient,
        },
      ],
    }).compile();

    service = module.get<OrderService>(OrderService);
    userClient = module.get<ClientProxy>('USER_SERVICE');
    inventoryClient = module.get<ClientProxy>('INVENTORY_SERVICE');
  });

  it('should process order successfully', async () => {
    // Setup mocks
    (userClient.send as jest.Mock).mockReturnValue(
      of({ id: 1, email: 'test@example.com' })
    );
    (inventoryClient.send as jest.Mock).mockReturnValue(
      of({ available: true })
    );

    const orderData = {
      userId: 1,
      items: [{ id: 1, quantity: 2 }],
      total: 100,
    };

    const result = await service.processOrder(orderData);

    expect(result).toBeDefined();
    expect(userClient.send).toHaveBeenCalledWith(
      { cmd: 'get_user' },
      { id: 1 }
    );
    expect(inventoryClient.send).toHaveBeenCalledWith(
      { cmd: 'check_availability' },
      { items: orderData.items }
    );
  });
});

docs

client-proxies.md

context-objects.md

exceptions.md

grpc.md

index.md

message-patterns.md

modules.md

transports.md

tile.json