Overview

Create a hybrid application running HTTP and microservice transports. Fully configurable with type-safe options for TCP, Redis, RabbitMQ, and NATS.

Features

  • Type-safe transport configuration
  • TCP/Redis/RabbitMQ/NATS support
  • Class-based MicroserviceServer setup
  • Hybrid or standalone modes
  • Graceful shutdown handling

Installation

npx hanma add nest-microservice --framework nest

Dependencies

@nestjs/microservices, @nestjs/config

Code

// ============================================================================
// microservice/microservice.interface.ts - Configuration interfaces
// ============================================================================

import { Transport } from "@nestjs/microservices";

export interface TcpTransportConfig {
  type: "tcp";
  host?: string;
  port?: number;
  retryAttempts?: number;
  retryDelay?: number;
}

export interface RedisTransportConfig {
  type: "redis";
  host?: string;
  port?: number;
  password?: string;
  retryAttempts?: number;
  retryDelay?: number;
}

export interface RabbitMqTransportConfig {
  type: "rmq";
  urls?: string[];
  queue?: string;
  queueOptions?: {
    durable?: boolean;
  };
  prefetchCount?: number;
}

export interface NatsTransportConfig {
  type: "nats";
  servers?: string[];
}

export type TransportConfig =
  | TcpTransportConfig
  | RedisTransportConfig
  | RabbitMqTransportConfig
  | NatsTransportConfig;

export interface MicroserviceOptions {
  /** Transports to enable (default: [{ type: "tcp" }]) */
  transports?: TransportConfig[];
  /** Enable HTTP server alongside microservices (default: true) */
  enableHttp?: boolean;
  /** HTTP port (default: from config or 3000) */
  httpPort?: number;
  /** Global API prefix for HTTP (default: "api") */
  globalPrefix?: string;
  /** Enable request logging (default: true) */
  enableLogs?: boolean;
  /** Enable validation pipe (default: true) */
  enableValidation?: boolean;
}

// ============================================================================
// microservice/microservice.setup.ts - Microservice bootstrap class
// ============================================================================

import { NestFactory } from "@nestjs/core";
import {
  MicroserviceOptions as NestMicroserviceOptions,
  Transport,
} from "@nestjs/microservices";
import {
  Logger,
  ValidationPipe,
  DynamicModule,
  ForwardReference,
  Type,
} from "@nestjs/common";
import { ConfigService } from "@nestjs/config";
import type {
  MicroserviceOptions,
  TransportConfig,
  TcpTransportConfig,
  RedisTransportConfig,
  RabbitMqTransportConfig,
  NatsTransportConfig,
} from "./microservice.interface";

type ModuleType = Type<any> | DynamicModule | ForwardReference;

export class MicroserviceServer {
  private static logger = new Logger("Microservice");
  private static app: any;

  /**
   * Bootstrap hybrid application (HTTP + Microservices)
   */
  static async create(module: ModuleType, options: MicroserviceOptions = {}) {
    const {
      transports = [{ type: "tcp" }],
      enableHttp = true,
      httpPort,
      globalPrefix = "api",
      enableLogs = true,
      enableValidation = true,
    } = options;

    // Create main application
    const app = await NestFactory.create(module, {
      logger: enableLogs
        ? ["log", "error", "warn", "debug", "verbose"]
        : ["error", "warn"],
    });

    this.app = app;
    const config = app.get(ConfigService);

    // Setup global prefix for HTTP
    if (enableHttp) {
      app.setGlobalPrefix(globalPrefix);
    }

    // Setup validation
    if (enableValidation) {
      app.useGlobalPipes(
        new ValidationPipe({
          whitelist: true,
          forbidNonWhitelisted: true,
          transform: true,
          transformOptions: {
            enableImplicitConversion: true,
          },
        })
      );
    }

    // Connect transports
    for (const transport of transports) {
      const transportOptions = this.buildTransportOptions(transport, config);
      app.connectMicroservice<NestMicroserviceOptions>(transportOptions);
      this.logger.log(`${transport.type.toUpperCase()} transport configured`);
    }

    // Setup request logging for microservices
    if (enableLogs) {
      this.setupLogging();
    }

    // Graceful shutdown
    this.setupGracefulShutdown();

    // Start all microservices
    await app.startAllMicroservices();
    this.logger.log("All microservices are listening");

    // Start HTTP server if enabled
    if (enableHttp) {
      const port = httpPort ?? config.get<number>("PORT", 3000);
      await app.listen(port);
      this.logger.log(`HTTP server listening on http://localhost:${port}/${globalPrefix}`);
    }

    return app;
  }

  /**
   * Create standalone microservice (no HTTP)
   */
  static async createStandalone(
    module: ModuleType,
    transport: TransportConfig = { type: "tcp" }
  ) {
    const app = await NestFactory.create(module, { logger: false });
    const config = app.get(ConfigService);
    
    const transportOptions = this.buildTransportOptions(transport, config);
    const microservice = await NestFactory.createMicroservice(
      module,
      transportOptions
    );

    microservice.useGlobalPipes(
      new ValidationPipe({ whitelist: true, transform: true })
    );
    microservice.enableShutdownHooks();

    await microservice.listen();
    this.logger.log(
      `Standalone ${transport.type.toUpperCase()} microservice is listening`
    );

    return microservice;
  }

  private static buildTransportOptions(
    transport: TransportConfig,
    config: ConfigService
  ): NestMicroserviceOptions {
    switch (transport.type) {
      case "tcp":
        return this.buildTcpOptions(transport, config);
      case "redis":
        return this.buildRedisOptions(transport, config);
      case "rmq":
        return this.buildRabbitMqOptions(transport, config);
      case "nats":
        return this.buildNatsOptions(transport, config);
      default:
        throw new Error(`Unknown transport type: ${(transport as any).type}`);
    }
  }

  private static buildTcpOptions(
    transport: TcpTransportConfig,
    config: ConfigService
  ): NestMicroserviceOptions {
    return {
      transport: Transport.TCP,
      options: {
        host: transport.host ?? config.get<string>("TCP_HOST", "0.0.0.0"),
        port: transport.port ?? config.get<number>("TCP_PORT", 3001),
        retryAttempts: transport.retryAttempts ?? 5,
        retryDelay: transport.retryDelay ?? 1000,
      },
    };
  }

  private static buildRedisOptions(
    transport: RedisTransportConfig,
    config: ConfigService
  ): NestMicroserviceOptions {
    return {
      transport: Transport.REDIS,
      options: {
        host: transport.host ?? config.get<string>("REDIS_HOST", "localhost"),
        port: transport.port ?? config.get<number>("REDIS_PORT", 6379),
        password: transport.password ?? config.get<string>("REDIS_PASSWORD"),
        retryAttempts: transport.retryAttempts ?? 5,
        retryDelay: transport.retryDelay ?? 1000,
      },
    };
  }

  private static buildRabbitMqOptions(
    transport: RabbitMqTransportConfig,
    config: ConfigService
  ): NestMicroserviceOptions {
    return {
      transport: Transport.RMQ,
      options: {
        urls: transport.urls ?? [
          config.get<string>("RABBITMQ_URL", "amqp://localhost:5672"),
        ],
        queue:
          transport.queue ?? config.get<string>("RABBITMQ_QUEUE", "main_queue"),
        queueOptions: transport.queueOptions ?? { durable: true },
        noAck: false,
        prefetchCount: transport.prefetchCount ?? 1,
      },
    };
  }

  private static buildNatsOptions(
    transport: NatsTransportConfig,
    config: ConfigService
  ): NestMicroserviceOptions {
    return {
      transport: Transport.NATS,
      options: {
        servers: transport.servers ?? [
          config.get<string>("NATS_URL", "nats://localhost:4222"),
        ],
      },
    };
  }

  private static setupLogging() {
    // Note: For detailed microservice request logging, 
    // use an interceptor in your app module
    this.logger.log("Request logging enabled");
  }

  private static setupGracefulShutdown() {
    this.app.enableShutdownHooks();

    const shutdown = async (signal: string) => {
      this.logger.log(`${signal} received, shutting down gracefully...`);
      try {
        await this.app.close();
        this.logger.log("Application closed successfully");
        process.exit(0);
      } catch (error) {
        this.logger.error("Error during shutdown", error);
        process.exit(1);
      }
    };

    process.on("SIGTERM", () => shutdown("SIGTERM"));
    process.on("SIGINT", () => shutdown("SIGINT"));
  }

  /**
   * Get the application instance
   */
  static getApp() {
    return this.app;
  }
}

/* USAGE in main.ts:

import { MicroserviceServer } from "./microservice/microservice.setup";
import { AppModule } from "./app.module";

// Hybrid app with TCP + Redis
MicroserviceServer.create(AppModule, {
  transports: [
    { type: "tcp", port: 3001 },
    { type: "redis", host: "localhost", port: 6379 },
  ],
  enableHttp: true,
  httpPort: 3000,
  globalPrefix: "api",
  enableLogs: true,
});

// Standalone TCP microservice (no HTTP)
MicroserviceServer.createStandalone(AppModule, { type: "tcp", port: 3001 });

// RabbitMQ only
MicroserviceServer.create(AppModule, {
  transports: [
    { type: "rmq", queue: "orders", queueOptions: { durable: true } },
  ],
  enableHttp: false,
});


// Example Controller with message patterns:

import { Controller } from "@nestjs/common";
import { MessagePattern, EventPattern, Payload, Ctx, RmqContext } from "@nestjs/microservices";

@Controller()
export class OrdersController {
  @MessagePattern("orders.create")
  async createOrder(@Payload() data: { productId: string; quantity: number }) {
    // Process order - returns response to caller
    return { orderId: crypto.randomUUID(), status: "created", ...data };
  }

  @MessagePattern("orders.findOne")
  async findOrder(@Payload() data: { id: string }) {
    return { orderId: data.id, status: "found" };
  }

  @EventPattern("orders.shipped")
  async handleOrderShipped(@Payload() data: { orderId: string }) {
    // Handle event - no response expected
    console.log(`Order ${data.orderId} was shipped`);
  }

  // RabbitMQ with manual acknowledgement
  @MessagePattern("orders.process")
  async processOrder(
    @Payload() data: any,
    @Ctx() context: RmqContext
  ) {
    const channel = context.getChannelRef();
    const originalMsg = context.getMessage();
    
    try {
      // Process the order
      const result = { processed: true, ...data };
      channel.ack(originalMsg);
      return result;
    } catch (error) {
      channel.nack(originalMsg);
      throw error;
    }
  }
}

*/

Usage

MicroserviceServer.create(AppModule, {\n  transports: [{ type: 'tcp' }, { type: 'redis' }],\n  enableHttp: true,\n});

Dependencies

Frequently Asked Questions

How do I install Microservice Bootstrap?
Run the following command: npx hanma add nest-microservice --framework nest
What dependencies are required?
This snippet requires: @nestjs/microservices, @nestjs/config. Make sure to install them before using the snippet.
How do I use this snippet?
Copy the code and paste it into your project. The snippet will be saved to src/microservice/microservice.setup.ts.
Can I customize this snippet?
Yes! All snippets are copy-paste code that you own. Feel free to modify them to fit your needs.
Does this work with other frameworks?
This snippet is designed for the specific framework. Check the other framework pages for equivalent implementations.