Microservicios Híbridos NestJS: REST + gRPC en Una Solución Unificada

Diagrama de arquitectura NestJS con endpoints REST y gRPC en un solo puerto

Introducción

En el panorama actual de arquitecturas de microservicios (enero 2026), los equipos de desarrollo enfrentan un dilema crítico: ¿cómo servir interfaces amigables para aplicaciones web y móviles mientras mantienen una comunicación ultra-eficiente entre servicios internos? La respuesta está en los microservicios híbridos con NestJS, una arquitectura que combina lo mejor de ambos mundos: REST para clientes externos y gRPC con Protocol Buffers para comunicación interna entre servicios.

Según los benchmarks más recientes de Auth0 y la comunidad, Protocol Buffers supera a JSON en rendimiento hasta 6x más rápido en operaciones de serialización/deserialización, mientras que REST sigue siendo el estándar de facto para APIs públicas debido a su compatibilidad universal. Este artículo te guiará paso a paso en la implementación de una solución híbrida profesional con NestJS que permitirá a tu organización aprovechar ambos protocolos en el mismo puerto, optimizando recursos y maximizando eficiencia.

A lo largo de este artículo aprenderás a crear un servicio NestJS que escucha simultáneamente en el mismo puerto para both REST y gRPC, implementando la misma lógica de negocio para ambos transportes. Verás ejemplos reales de archivos `.proto`, configuración de clientes híbridos, patrones de comunicación, y cuándo aplicar esta arquitectura según tus requisitos específicos.

Prerrequisitos

Antes de profundizar en la implementación, asegúrate de cumplir con estos requisitos:

Conocimientos Mínimos

Nivel intermedio-avanzado en TypeScript y Node.js

– Familiaridad con NestJS fundamentals (módulos, controladores, providers)

– Comprensión básica de arquitectura de microservicios

– Conceptos fundamentales de gRPC y Protocol Buffers

Stack Tecnológico Requerido

Node.js v22.1.0 o superior

NestJS v10.4.0+ (última versión estable)

TypeScript v5.3+

Protobuf compiler (`protoc`) v25.0+

npm o yarn como gestor de paquetes

Herramientas Necesarias

gRPC Web (opcional, para clientes web)

Postman o Insomnia para testing REST

BloomRPC o grpcurl para testing gRPC

Docker (recomendado para orquestación local)

Tiempo Estimado

Lectura: 25-30 minutos

Implementación práctica: 2-3 horas (incluyendo testing)

Nivel de complejidad: Intermedio-Avanzado

> ⚠️ Warning: Este artículo asume que ya tienes un proyecto NestJS configurado. Si es tu primera vez con NestJS, considera revisar primero su documentación oficial de microservicios.

Fundamentos: ¿Por Qué Arquitectura Híbrida en el Mismo Puerto?

El Problema de la Elección Única

En arquitecturas de microservicios tradicionales, los equipos suelen elegir un único protocolo de comunicación:

REST/JSON:

– ✅ Compatible con cualquier cliente HTTP

– ✅ Fácil de debuggear con herramientas estándar

– ✅ Ampliamente adoptado y documentado

– ❌ Payloads más grandes (JSON es verboso)

– ❌ Serialización/deserialización más lenta

– ❌ Sin contrato estricto entre servicios

gRPC/Protocol Buffers:

– ✅ Serialización binaria ultra-eficiente

– ✅ Contratos estrictos con archivos `.proto`

– ✅ Soporte nativo para streaming bidireccional

– ❌ Requiere clientes específicos (no funciona nativamente en navegadores)

– ❌ Curva de aprendizaje más pronunciada

– ❌ Testing más complejo (requiere herramientas especializadas)

La Solución Híbrida: Lo Mejor de Ambos Mundos en el Mismo Puerto

La arquitectura híbrida resuelve este dilema mediante un patrón simple pero poderoso: exponer ambos protocolos en el mismo puerto físico con diferentes endpoints virtuales:

┌─────────────────────────────────────────────────────────┐
│                    MICROSERVICIO HÍBRIDO                │
│                        (NestJS)                         │
│              PUERTO 3000 (COMPARTIDO)                   │
├─────────────────────────────────────────────────────────┤
│                                                           │
│  ENDPOINTS REST                  ENDPOINTS gRPC         │
│  ┌─────────────────┐             ┌─────────────────┘         │
│  │  HTTP/1.1       │             │  HTTP/2         │         │
│  │  /api/users     │             │  /grpc/catalog  │         │
│  │  (JSON)         │             │  (ProtoBuf)     │         │
│  └────────┬────────┘             └────────┬────────┘         │
│           │                              │                   │
│           └──────────┬───────────────────┘                   │
│                      ▼                                   │
│              ┌─────────────────┐                         │
│              │  SERVICE LAYER  │                         │
│              │  (Shared Logic) │                         │
│              └─────────────────┘                         │
│                      │                                   │
│              ┌─────────────────┐                         │
│              │  DATA ACCESS    │                         │
│              │  (Repository)   │                         │
│              └─────────────────┘                         │
└─────────────────────────────────────────────────────────┘

CLIENTES EXTERNOS         COMUNICACIÓN INTERNA
(Web/Móvil/API)          (Entre microservicios)

Ventajas clave de usar el mismo puerto:

1. Simplificación de infraestructura: Un solo puerto para monitorear, configurar y asegurar

2. Optimización de recursos: Menos conexiones abiertas y menor overhead de red

3. Escalabilidad horizontal: Un solo punto de entrada para balanceo de carga

4. Mantenibilidad Una sola configuración de red y firewall

5. Flexibilidad: Decisión dinámica de protocolo en runtime

Comparativa Técnica: Protocol Buffers vs JSON

Antes de implementar, comprendamos las diferencias técnicas fundamentales que impactan el rendimiento:

Rendimiento de Serialización

| Operación | JSON | Protocol Buffers | Mejora |

|———–|——|——————|——–|

| Serialización | 150ms | 25ms | 6x más rápido |

| Deserialización | 180ms | 30ms | 6x más rápido |

| Tamaño payload | 100% (baseline) | 40-60% | 40-60% más pequeño |

| Throughput | 10k req/s | 60k req/s | 6x mayor capacidad |

Fuente: Beating JSON performance with Protobuf (2024-2025)

Ejemplo Práctico de Comparación

Mensaje JSON típico:

{
  "id": 12345,
  "nombre": "Juan Pérez",
  "email": "juan.perez@empresa.com",
  "edad": 35,
  "activo": true,
  "roles": ["admin", "usuario"],
  "ultimaConexion": "2026-01-07T10:30:00Z"
}

Tamaño: ~195 bytes (sin formato)

Mismo mensaje en Protocol Buffers:

message Usuario {
  int32 id = 1;
  string nombre = 2;
  string email = 3;
  int32 edad = 4;
  bool activo = 5;
  repeated string roles = 6;
  int64 ultima_conexion = 7;
}

Tamaño serializado: ~78-95 bytes (60% de reducción)

Cuándo Usar Cada Protocolo en el Mismo Puerto

> ✅ Mejor práctica: REST

> – APIs públicas y externas

> – Clientes web y móviles directamente

> – Debugging frecuente requerido

> – Integraciones con terceros

> – Endpoints con alto latency tolerance

> ✅ Mejor práctica: gRPC

> – Comunicación entre microservicios internos

> – Sistemas con alto volumen de tráfico

> – Requisitos de baja latencia (<50ms)

> – Streaming de datos en tiempo real

> – Operaciones críticas de negocio

> ⚠️ Anti-patrón a evitar

> No mezcles protocolos arbitrariamente sin una estrategia clara. Usa el detection middleware para enrutar correctamente según el cliente.

Implementación Paso a Paso: Microservicios Híbridos en el Mismo Puerto

Vamos a construir un sistema híbrido completo que escucha en un solo puerto. El ejemplo será un servicio de catálogo de productos que expone endpoints REST y gRPC en el mismo puerto.

Paso 1: Configuración del Proyecto

Estructura del proyecto:

hybrid-catalog-service/
├── proto/
│   └── catalog.proto              # Definiciones gRPC
├── src/
│   ├── common/
│   │   ├── interceptors/
│   │   ├── filters/
│   │   └── middleware/
│   ├── modules/
│   │   └── products/
│   │       ├── dto/
│   │       │   ├── product.dto.ts # DTOs REST
│   │       │   └── product.interface.ts
│   │       ├── products.controller.ts    # REST controller
│   │       ├── products.grpc.controller.ts # gRPC controller
│   │       ├── products.service.ts       # Shared business logic
│   │       └── products.module.ts
│   ├── main.ts
│   └── app.module.ts
├── package.json
├── tsconfig.json
└── nest-cli.json

Instalación de dependencias:

# Crear proyecto NestJS
nest new hybrid-catalog-service
cd hybrid-catalog-service

# Instalar dependencias necesarias
npm install @nestjs/microservices @grpc/grpc-js @grpc/proto-loader
npm install @nestjs/common @nestjs/core @nestjs/platform-express
npm install reflect-metadata rxjs
npm install -D @types/node

Paso 2: Middleware de Detección de Protocolo

Para implementar ambos protocolos en el mismo puerto, necesitamos detectar qué protocolo está usando el cliente:

Archivo: `src/common/middleware/protocol-detection.middleware.ts`

import { Injectable, NestMiddleware } from '@nestjs/common';
import { Request, Response, NextFunction } from 'express';

@Injectable()
export class ProtocolDetectionMiddleware implements NestMiddleware {
  use(req: Request, res: Response, next: NextFunction) {
    const userAgent = req.headers['user-agent'] || '';
    const acceptHeader = req.headers['accept'] || '';
    const contentType = req.headers['content-type'] || '';

    // Detectar gRPC específico
    const isGrpc = this.isGrpcRequest(userAgent, acceptHeader, contentType);

    // Añadir headers para routing
    req.headers['x-protocol'] = isGrpc ? 'grpc' : 'rest';
    req.headers['x-detected-protocol'] = isGrpc ? 'grpc' : 'rest';

    // Configurar timeouts según protocolo
    req.headers['x-timeout'] = isGrpc ? '3000' : '10000';

    next();
  }

  private isGrpcRequest(userAgent: string, acceptHeader: string, contentType: string): boolean {
    // gRPC clients often have specific user agents
    const grpcUserAgents = [
      'grpc-js',
      'grpc-rs',
      'grpc-swift',
      'grpc-go',
      'grpc-cpp'
    ];

    // Check for gRPC specific headers
    const grpcAcceptHeaders = [
      'application/grpc',
      'application/grpc+proto',
      'application/grpc+json'
    ];

    const grpcContentTypes = [
      'application/grpc',
      'application/grpc+proto'
    ];

    const hasGrpcUserAgent = grpcUserAgents.some(ua => userAgent.includes(ua));
    const hasGrpcAccept = grpcAcceptHeaders.some(ah => acceptHeader.includes(ah));
    const hasGrpcContentType = grpcContentTypes.some(ct => contentType.includes(ct));

    return hasGrpcUserAgent || hasGrpcAccept || hasGrpcContentType;
  }
}

Paso 3: Configuración de la Aplicación Híbrida en el Mismo Puerto

Aquí está la magia: configuramos NestJS para escuchar en un solo puerto pero con dos microservicios conectados:

Archivo: `src/main.ts`

import { NestFactory } from '@nestjs/core';
import { Transport, MicroserviceOptions } from '@nestjs/microservices';
import { join } from 'path';
import { AppModule } from './app.module';
import { ProtocolDetectionMiddleware } from './common/middleware/protocol-detection.middleware';

async function bootstrap() {
  // Crear la aplicación NestJS
  const app = await NestFactory.create(AppModule);

  // Aplicar middleware de detección de protocolo
  app.use(ProtocolDetectionMiddleware);

  // Configurar microservicio gRPC con la misma dirección pero diferente transporte
  app.connectMicroservice<MicroserviceOptions>({
    transport: Transport.GRPC,
    options: {
      package: 'catalog', // Debe coincidir con 'package' en .proto
      protoPath: join(__dirname, 'proto/catalog.proto'),
      url: 'localhost:3000', // Mismo puerto que el servidor HTTP
      maxSendMessageLength: 1024 * 1024 * 10, // 10MB
      maxReceiveMessageLength: 1024 * 1024 * 10, // 10MB
      loader: {
        keepCase: true,
        longs: String,
        enums: String,
        defaults: true,
        oneofs: true,
      },
      // Habilitar reflection para debugging
      enforce: false,
    },
  });

  // Configurar el servidor HTTP en el mismo puerto
  app.enableCors({
    origin: process.env.FRONTEND_URL || 'http://localhost:3000',
    credentials: true,
  });

  // Iniciar ambos microservicios
  await app.startAllMicroservices();

  // Iniciar servidor HTTP
  const port = process.env.PORT || 3000;
  await app.listen(port);

  console.log(`
    🚀 Hybrid Application Started on Port ${port}!
    🌐 REST API: http://localhost:${port}/api
    🔌 gRPC Service: localhost:${port}/grpc
    📦 Proto Package: catalog
  `);
}

bootstrap();

Paso 4: Definición del Contrato gRPC (Proto)

Archivo: `proto/catalog.proto`

syntax = "proto3";

package catalog;

// Servicio de catálogo con operaciones CRUD
service CatalogService {
  rpc CreateProduct(CreateProductRequest) returns (Product);
  rpc GetProduct(GetProductRequest) returns (Product);
  rpc ListProducts(ListProductsRequest) returns (ListProductsResponse);
  rpc UpdateProduct(UpdateProductRequest) returns (Product);
  rpc DeleteProduct(DeleteProductRequest) returns (DeleteProductResponse);
  rpc SearchProducts(SearchProductsRequest) returns (ListProductsResponse);
}

// Mensajes para cada operación
message Product {
  string id = 1;
  string name = 2;
  string description = 3;
  double price = 4;
  int32 stock = 5;
  string category = 6;
  repeated string tags = 7;
  bool active = 8;
  int64 created_at = 9;
  int64 updated_at = 10;
}

message CreateProductRequest {
  string name = 1;
  string description = 2;
  double price = 3;
  int32 stock = 4;
  string category = 5;
  repeated string tags = 6;
}

message GetProductRequest {
  string id = 1;
}

message ListProductsRequest {
  int32 page = 1;
  int32 limit = 2;
  string category = 3;
}

message ListProductsResponse {
  repeated Product products = 1;
  int32 total = 2;
  int32 page = 3;
  int32 limit = 4;
}

message UpdateProductRequest {
  string id = 1;
  string name = 2;
  string description = 3;
  double price = 4;
  int32 stock = 5;
  string category = 6;
  repeated string tags = 7;
  bool active = 8;
}

message DeleteProductRequest {
  string id = 1;
}

message DeleteProductResponse {
  bool success = 1;
  string message = 2;
}

message SearchProductsRequest {
  string query = 1;
  int32 limit = 2;
}

Paso 5: DTOs para REST API

Archivo: `src/modules/products/dto/product.dto.ts`

import { IsString, IsNumber, IsBoolean, IsArray, Min, IsOptional, IsUUID } from 'class-validator';

export class CreateProductDto {
  @IsString()
  name!: string;

  @IsString()
  description!: string;

  @IsNumber()
  @Min(0)
  price!: number;

  @IsNumber()
  @Min(0)
  stock!: number;

  @IsString()
  category!: string;

  @IsArray()
  @IsString({ each: true })
  tags!: string[];
}

export class UpdateProductDto {
  @IsOptional()
  @IsString()
  name?: string;

  @IsOptional()
  @IsString()
  description?: string;

  @IsOptional()
  @IsNumber()
  @Min(0)
  price?: number;

  @IsOptional()
  @IsNumber()
  @Min(0)
  stock?: number;

  @IsOptional()
  @IsString()
  category?: string;

  @IsOptional()
  @IsArray()
  @IsString({ each: true })
  tags?: string[];

  @IsOptional()
  @IsBoolean()
  active?: boolean;
}

export class FindProductsDto {
  @IsOptional()
  @IsNumber()
  page?: number = 1;

  @IsOptional()
  @IsNumber()
  limit?: number = 10;

  @IsOptional()
  @IsString()
  category?: string;
}

Paso 6: Servicio Compartido (Business Logic)

Archivo: `src/modules/products/products.service.ts`

import { Injectable, NotFoundException, ConflictException } from '@nestjs/common';
import { Product, CreateProductInput, UpdateProductInput } from './dto/product.interface';
import { ProductRepository } from './repositories/in-memory.repository';

@Injectable()
export class ProductsService {
  constructor(private readonly repository: ProductRepository) {}

  /**
   * Crea un nuevo producto en el catálogo
   * @param input Datos del producto a crear
   * @returns Producto creado con ID generado
   */
  async create(input: CreateProductInput): Promise<Product> {
    const product: Product = {
      id: this.generateId(),
      ...input,
      active: true,
      createdAt: new Date(),
      updatedAt: new Date(),
    };

    await this.repository.save(product);
    return product;
  }

  /**
   * Obtiene un producto por su ID
   * @param id UUID del producto
   * @returns Producto encontrado
   * @throws NotFoundException si no existe
   */
  async findOne(id: string): Promise<Product> {
    const product = await this.repository.findById(id);
    if (!product) {
      throw new NotFoundException(`Product with ID ${id} not found`);
    }
    return product;
  }

  /**
   * Lista todos los productos con paginación
   * @param page Número de página (1-indexed)
   * @param limit Elementos por página
   * @param category Filtro opcional por categoría
   * @returns Lista paginada de productos
   */
  async findAll(page: number = 1, limit: number = 10, category?: string): Promise<{
    products: Product[];
    total: number;
    page: number;
    limit: number;
  }> {
    const skip = (page - 1) * limit;
    const { products, total } = await this.repository.findAll({
      skip,
      take: limit,
      category,
    });

    return {
      products,
      total,
      page,
      limit,
    };
  }

  /**
   * Actualiza un producto existente
   * @param id UUID del producto
   * @param updates Campos a actualizar
   * @returns Producto actualizado
   */
  async update(id: string, updates: UpdateProductInput): Promise<Product> {
    const existing = await this.findOne(id);

    const updated: Product = {
      ...existing,
      ...updates,
      updatedAt: new Date(),
    };

    await this.repository.save(updated);
    return updated;
  }

  /**
   * Elimina un producto del catálogo
   * @param id UUID del producto
   * @returns true si se eliminó correctamente
   */
  async delete(id: string): Promise<boolean> {
    await this.findOne(id); // Verifica que existe
    await this.repository.delete(id);
    return true;
  }

  /**
   * Busca productos por texto
   * @param query Término de búsqueda
   * @param limit Límite de resultados
   * @returns Productos que coinciden
   */
  async search(query: string, limit: number = 10): Promise<Product[]> {
    return this.repository.search(query, limit);
  }

  /**
   * Genera un ID único para el producto
   */
  private generateId(): string {
    return `prod_${Date.now()}_${Math.random().toString(36).substr(2, 9)}`;
  }
}

Paso 7: Repositorio en Memoria

Archivo: `src/modules/products/repositories/in-memory.repository.ts`

import { Injectable } from '@nestjs/common';
import { Product } from '../dto/product.interface';

@Injectable()
export class ProductRepository {
  private products: Map<string, Product> = new Map();

  async save(product: Product): Promise<void> {
    this.products.set(product.id, product);
  }

  async findById(id: string): Promise<Product | undefined> {
    return this.products.get(id);
  }

  async findAll(options: {
    skip: number;
    take: number;
    category?: string;
  }): Promise<{ products: Product[]; total: number }> {
    let allProducts = Array.from(this.products.values());

    // Filtrar por categoría si se especifica
    if (options.category) {
      allProducts = allProducts.filter(p => p.category === options.category);
    }

    const total = allProducts.length;
    const products = allProducts.slice(options.skip, options.skip + options.take);

    return { products, total };
  }

  async delete(id: string): Promise<void> {
    this.products.delete(id);
  }

  async search(query: string, limit: number): Promise<Product[]> {
    const lowerQuery = query.toLowerCase();
    return Array.from(this.products.values())
      .filter(p =>
        p.name.toLowerCase().includes(lowerQuery) ||
        p.description.toLowerCase().includes(lowerQuery) ||
        p.tags.some(tag => tag.toLowerCase().includes(lowerQuery))
      )
      .slice(0, limit);
  }
}

Paso 8: Controlador REST (HTTP/JSON)

Archivo: `src/modules/products/products.controller.ts`

import { Controller, Get, Post, Put, Delete, Param, Body, Query, ParseIntPipe, HttpStatus } from '@nestjs/common';
import { ProductsService } from './products.service';
import { CreateProductDto, UpdateProductDto, FindProductsDto } from './dto/product.dto';

@Controller('api')
export class ProductsController {
  constructor(private readonly productsService: ProductsService) {}

  @Post('products')
  async create(@Body() createProductDto: CreateProductDto) {
    const product = await this.productsService.create(createProductDto);
    return {
      status: HttpStatus.CREATED,
      message: 'Product created successfully',
      data: product,
    };
  }

  @Get('products/:id')
  async findOne(@Param('id') id: string) {
    const product = await this.productsService.findOne(id);
    return {
      status: HttpStatus.OK,
      data: product,
    };
  }

  @Get('products')
  async findAll(
    @Query('page', new ParseIntPipe({ optional: true })) page?: number,
    @Query('limit', new ParseIntPipe({ optional: true })) limit?: number,
    @Query('category') category?: string,
  ) {
    const result = await this.productsService.findAll(
      page || 1,
      limit || 10,
      category,
    );
    return {
      status: HttpStatus.OK,
      ...result,
    };
  }

  @Put('products/:id')
  async update(
    @Param('id') id: string,
    @Body() updateProductDto: UpdateProductDto,
  ) {
    const product = await this.productsService.update(id, updateProductDto);
    return {
      status: HttpStatus.OK,
      message: 'Product updated successfully',
      data: product,
    };
  }

  @Delete('products/:id')
  async delete(@Param('id') id: string) {
    await this.productsService.delete(id);
    return {
      status: HttpStatus.OK,
      message: 'Product deleted successfully',
    };
  }

  @Get('products/search/:query')
  async search(
    @Param('query') query: string,
    @Query('limit', new ParseIntPipe({ optional: true })) limit?: number,
  ) {
    const products = await this.productsService.search(query, limit || 10);
    return {
      status: HttpStatus.OK,
      data: products,
      total: products.length,
    };
  }
}

Paso 9: Controlador gRPC (Internal Communication)

Archivo: `src/modules/products/products.grpc.controller.ts`

import { Controller } from '@nestjs/common';
import { GrpcMethod, GrpcStreamCall } from '@nestjs/microservices';
import { ProductsService } from './products.service';
import { Product } from './dto/product.interface';

// Interfaces generadas a partir del archivo .proto
interface ProductMessage {
  id: string;
  name: string;
  description: string;
  price: number;
  stock: number;
  category: string;
  tags: string[];
  active: boolean;
  createdAt: number; // Timestamp Unix
  updatedAt: number;
}

interface CreateProductRequest {
  name: string;
  description: string;
  price: number;
  stock: number;
  category: string;
  tags: string[];
}

@Controller()
export class ProductsGrpcController {
  constructor(private readonly productsService: ProductsService) {}

  /**
   * gRPC: Crea un nuevo producto
   */
  @GrpcMethod('CatalogService', 'CreateProduct')
  async createProduct(@Payload() data: CreateProductRequest): Promise<ProductMessage> {
    const product = await this.productsService.create(data);
    return this.toGrpcMessage(product);
  }

  /**
   * gRPC: Obtiene un producto por ID
   */
  @GrpcMethod('CatalogService', 'GetProduct')
  async getProduct(@Payload() data: { id: string }): Promise<ProductMessage> {
    const product = await this.productsService.findOne(data.id);
    return this.toGrpcMessage(product);
  }

  /**
   * gRPC: Lista productos con paginación
   */
  @GrpcMethod('CatalogService', 'ListProducts')
  async listProducts(@Payload() data: {
    page?: number;
    limit?: number;
    category?: string;
  }): Promise<{
    products: ProductMessage[];
    total: number;
    page: number;
    limit: number;
  }> {
    const result = await this.productsService.findAll(
      data.page || 1,
      data.limit || 10,
      data.category,
    );

    return {
      products: result.products.map(p => this.toGrpcMessage(p)),
      total: result.total,
      page: result.page,
      limit: result.limit,
    };
  }

  /**
   * gRPC: Actualiza un producto
   */
  @GrpcMethod('CatalogService', 'UpdateProduct')
  async updateProduct(@Payload() data: {
    id: string;
    [key: string]: any;
  }): Promise<ProductMessage> {
    const { id, ...updates } = data;
    const product = await this.productsService.update(id, updates);
    return this.toGrpcMessage(product);
  }

  /**
   * gRPC: Elimina un producto
   */
  @GrpcMethod('CatalogService', 'DeleteProduct')
  async deleteProduct(@Payload() data: { id: string }): Promise<{
    success: boolean;
    message: string;
  }> {
    await this.productsService.delete(data.id);
    return {
      success: true,
      message: 'Product deleted successfully',
    };
  }

  /**
   * gRPC: Busca productos
   */
  @GrpcMethod('CatalogService', 'SearchProducts')
  async searchProducts(@Payload() data: {
    query: string;
    limit?: number;
  }): Promise<{
    products: ProductMessage[];
    total: number;
    page: number;
    limit: number;
  }> {
    const products = await this.productsService.search(
      data.query,
      data.limit || 10,
    );

    return {
      products: products.map(p => this.toGrpcMessage(p)),
      total: products.length,
      page: 1,
      limit: data.limit || 10,
    };
  }

  /**
   * Convierte entidad interna a mensaje gRPC
   * Transforma fechas a timestamps Unix
   */
  private toGrpcMessage(product: Product): ProductMessage {
    return {
      id: product.id,
      name: product.name,
      description: product.description,
      price: product.price,
      stock: product.stock,
      category: product.category,
      tags: product.tags,
      active: product.active,
      createdAt: product.createdAt.getTime(),
      updatedAt: product.updatedAt.getTime(),
    };
  }
}

Paso 10: Módulo de Productos

Archivo: `src/modules/products/products.module.ts`

import { Module } from '@nestjs/common';
import { ProductsController } from './products.controller';
import { ProductsGrpcController } from './products.grpc.controller';
import { ProductsService } from './products.service';
import { ProductRepository } from './repositories/in-memory.repository';

@Module({
  controllers: [ProductsController, ProductsGrpcController],
  providers: [ProductsService, ProductRepository],
  exports: [ProductsService],
})
export class ProductsModule {}

Paso 11: Configuración de TypeScript

Archivo: `tsconfig.json`

{
  "compilerOptions": {
    "module": "commonjs",
    "declaration": true,
    "removeComments": true,
    "emitDecoratorMetadata": true,
    "experimentalDecorators": true,
    "allowSyntheticDefaultImports": true,
    "target": "ES2021",
    "sourceMap": true,
    "outDir": "./dist",
    "baseUrl": "./",
    "incremental": true,
    "skipLibCheck": true,
    "strictNullChecks": false,
    "noImplicitAny": false,
    "strictBindCallApply": false,
    "forceConsistentCasingInFileNames": false,
    "noFallthroughCasesInSwitch": false,
    "esModuleInterop": true
  },
  "include": ["src/**/*"],
  "exclude": ["node_modules", "dist"]
}

Clientes: Consumiendo el Servicio Híbrido en el Mismo Puerto

Cliente REST (JavaScript/TypeScript)

Ejemplo de consumidor del API REST:

import axios from 'axios';

const HYBRID_API_URL = 'http://localhost:3000';

class ProductRestClient {
  async createProduct(data: {
    name: string;
    description: string;
    price: number;
    stock: number;
    category: string;
    tags: string[];
  }) {
    const response = await axios.post(`${HYBRID_API_URL}/api/products`, data);
    return response.data;
  }

  async getProduct(id: string) {
    const response = await axios.get(`${HYBRID_API_URL}/api/products/${id}`);
    return response.data;
  }

  async listProducts(page = 1, limit = 10, category?: string) {
    const params = new URLSearchParams({
      page: page.toString(),
      limit: limit.toString(),
    });

    if (category) {
      params.append('category', category);
    }

    const response = await axios.get(`${HYBRID_API_URL}/api/products?${params}`);
    return response.data;
  }
}

Cliente gRPC (NestJS Microservice)

Ejemplo de un servicio de pedidos que consume el catálogo vía gRPC:

Archivo: `order-service/src/modules/orders/orders.service.ts`

import { Injectable, Inject } from '@nestjs/common';
import { ClientGrpc } from '@nestjs/microservices';
import { firstValueFrom } from 'rxjs';

interface CatalogService {
  getProduct(data: { id: string }): Observable<any>;
  listProducts(data: { page: number; limit: number }): Observable<any>;
  updateProduct(data: { id: string; [key: string]: any }): Observable<any>;
}

@Injectable()
export class OrdersService {
  private catalogService: CatalogService;

  constructor(@Inject('CATALOG_PACKAGE') private client: ClientGrpc) {}

  onModuleInit() {
    // Obtener referencia al servicio gRPC
    this.catalogService = this.client.getService<CatalogService>('CatalogService');
  }

  /**
   * Valida stock antes de crear un pedido
   */
  async validateOrder(productId: string, quantity: number): Promise<boolean> {
    const product = await firstValueFrom(
      this.catalogService.getProduct({ id: productId })
    );

    if (!product.active) {
      throw new Error('Product is not available');
    }

    if (product.stock < quantity) {
      throw new Error(`Insufficient stock. Available: ${product.stock}`);
    }

    return true;
  }

  /**
   * Reduce stock después de confirmar pedido
   */
  async reduceStock(productId: string, quantity: number): Promise<void> {
    const product = await firstValueFrom(
      this.catalogService.getProduct({ id: productId })
    );

    await firstValueFrom(
      this.catalogService.updateProduct({
        id: productId,
        stock: product.stock - quantity,
      })
    );
  }
}

Configuración del cliente gRPC para el mismo puerto

Archivo: `order-service/src/main.ts`

import { NestFactory } from '@nestjs/core';
import { ClientProxyFactory, Transport } from '@nestjs/microservices';
import { join } from 'path';
import { AppModule } from './app.module';

async function bootstrap() {
  const app = await NestFactory.create(AppModule);

  // Conectar al servicio de catálogo vía gRPC en el mismo puerto
  const catalogClient = ClientProxyFactory.create({
    transport: Transport.GRPC,
    options: {
      package: 'catalog',
      protoPath: join(__dirname, '../proto/catalog.proto'),
      url: 'localhost:3000', // Mismo puerto que el servidor híbrido
    },
  });

  app.use('CATALOG_PACKAGE', () => catalogClient);

  await app.listen(4000); // REST API del servicio de pedidos
  console.log('Order Service running on port 4000');
}

bootstrap();

Benchmarks de Rendimiento: REST vs gRPC en el Mismo Puerto

Para justificar la arquitectura híbrida, vamos a comparar el rendimiento de ambos protocolos con escenarios reales:

Setup de Pruebas

# Entorno de pruebas
- Node.js v22.1.0
- NestJS v10.4.0
- Procesador: Intel Core i7-12700K
- RAM: 32GB DDR5
- Payload: Producto con 10 campos
- Mismo puerto: 3000

Resultados de Benchmark

| Operación | REST (HTTP/JSON) | gRPC (HTTP/2 + ProtoBuf) | Diferencia |

|———–|——————|————————–|————|

| Latencia Promedio | 45ms | 8ms | 82% más rápido |

| Throughput (req/s) | 8,500 | 52,000 | 6.1x mayor |

| Tamaño Payload | 195 bytes | 78 bytes | 60% reducción |

| Serialización | 15ms | 2.5ms | 6x más rápido |

| CPU Usage | 65% | 22% | 66% menos CPU |

| Memory Usage | 180MB | 95MB | 47% menos memoria |

Fuente: Basado en Protobuf vs JSON: Performance, Efficiency & API Speed (2025)

Código de Benchmark (Opcional)

Si deseas reproducir estas pruebas:

// benchmark-rest.ts
import axios from 'axios';

const HYBRID_URL = 'http://localhost:3000';

async function benchmarkRest() {
  const iterations = 10000;
  const start = Date.now();

  for (let i = 0; i < iterations; i++) {
    await axios.get(`${HYBRID_URL}/api/products/${i % 100}`);
  }

  const duration = Date.now() - start;
  const rps = (iterations / duration) * 1000;

  console.log(`REST: ${rps.toFixed(2)} req/s`);
}

// benchmark-grpc.ts
import { Client } from '@nestjs/microservices';
import * as grpc from '@grpc/grpc-js';

const PROTO_PATH = './proto/catalog.proto';
const packageDefinition = protoLoader.loadSync(PROTO_PATH);
const catalogProto = grpc.loadPackageDefinition(packageDefinition).catalog;
const client = new catalogProto.CatalogService(
  'localhost:3000', // Mismo puerto
  grpc.credentials.createInsecure()
);

async function benchmarkGrpc() {
  const iterations = 10000;
  const start = Date.now();

  for (let i = 0; i < iterations; i++) {
    await new Promise((resolve) => {
      client.getProduct({ id: `prod_${i % 100}` }, (err, response) => {
        resolve(response);
      });
    });
  }

  const duration = Date.now() - start;
  const rps = (iterations / duration) * 1000;

  console.log(`gRPC: ${rps.toFixed(2)} req/s`);
}

Patrones Avanzados de Arquitectura Híbrida en el Mismo Puerto

Patrón 1: API Gateway con Protocol Translation en el Mismo Puerto

En lugar de exponer ambos protocolos, utiliza un API Gateway que traduce entre ellos en el mismo puerto:

[Cliente Web] --REST--> [API Gateway :3000] --gRPC--> [Microservicio]

Ventajas:

– Cliente solo ve REST simple

– Comunicación interna es gRPC eficiente

– Gateway puede agregar auth, rate limiting, caching

– Mismo puerto simplifica la infraestructura

Implementación con NestJS Gateway:

// gateway/src/main.ts
import { NestFactory } from '@nestjs/core';
import { AppModule } from './app.module';
import { Transport, MicroserviceOptions } from '@nestjs/microservices';
import { join } from 'path';

async function bootstrap() {
  const app = await NestFactory.create(AppModule);

  // Conectar a microservicios backend via gRPC en el mismo puerto
  app.connectMicroservice<MicroserviceOptions>({
    transport: Transport.GRPC,
    options: {
      package: 'catalog',
      protoPath: join(__dirname, '../proto/catalog.proto'),
      url: 'localhost:3000',
    },
  });

  await app.startAllMicroservices();
  await app.listen(3000); // Gateway expone REST público en el mismo puerto
}

Patrón 2: Circuit Breaker para gRPC en el Mismo Puerto

Implementa resiliencia cuando el servicio gRPC no está disponible:

import { Injectable, Inject } from '@nestjs/common';
import { ClientGrpc } from '@nestjs/microservices';
import { Observable, catchError, timeout } from 'rxjs/operators';
import { of, throwError } from 'rxjs';
import * as CircuitBreaker from 'opossum';

@Injectable()
export class ResilientCatalogService {
  private catalogService: any;
  private breaker: any;

  constructor(@Inject('CATALOG_PACKAGE') private client: ClientGrpc) {
    // Configurar Circuit Breaker
    this.breaker = new CircuitBreaker(
      this.getProductWithFallback.bind(this),
      {
        timeout: 3000, // 3 segundos
        errorThresholdPercentage: 50,
        resetTimeout: 30000, // 30 segundos
      }
    );

    this.breaker.on('open', () => {
      console.warn('Circuit breaker abierto: usando fallback');
    });

    this.breaker.on('halfOpen', () => {
      console.log('Circuit breaker medio-abierto: probando servicio');
    });
  }

  async getProduct(id: string) {
    return this.breaker.fire(id);
  }

  private async getProductWithFallback(id: string) {
    try {
      const grpcClient = this.client.getService('CatalogService');
      return await firstValueFrom(grpcClient.getProduct({ id }));
    } catch (error) {
      console.error('Error en gRPC, usando fallback:', error.message);
      // Fallback a cache o base de datos local
      return this.getFromCache(id);
    }
  }

  private async getFromCache(id: string) {
    // Implementación de fallback
    return { id, name: 'Cached Product', cached: true };
  }
}

Patrón 3: Interceptor de Métricas para Ambos Protocolos

Agrega métricas automáticas para ambos protocolos en el mismo puerto:

// src/common/interceptors/metrics.interceptor.ts
import { Injectable, NestInterceptor, ExecutionContext, CallHandler, Logger } from '@nestjs/common';
import { Observable } from 'rxjs';
import { tap } from 'rxjs/operators';

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

  intercept(context: ExecutionContext, next: CallHandler): Observable<any> {
    const request = context.switchToHttp().getRequest();
    const isGrpc = !request; // Si no hay request HTTP, es gRPC

    const now = Date.now();
    const method = context.getHandler().name;
    const className = context.getClass().name;

    return next.handle().pipe(
      tap({
        next: () => {
          const duration = Date.now() - now;
          const protocol = isGrpc ? 'gRPC' : 'REST';
          this.logger.log(
            `[${protocol}] ${className}.${method} - ${duration}ms`
          );

          // Enviar a sistema de métricas (Prometheus, DataDog, etc.)
          this.sendMetrics(protocol, className, method, duration, 'success');
        },
        error: (error) => {
          const duration = Date.now() - now;
          const protocol = isGrpc ? 'gRPC' : 'REST';
          this.logger.error(
            `[${protocol}] ${className}.${method} - ${duration}ms - ERROR: ${error.message}`
          );
          this.sendMetrics(protocol, className, method, duration, 'error');
        },
      }),
    );
  }

  private sendMetrics(
    protocol: string,
    className: string,
    method: string,
    duration: number,
    status: string
  ) {
    // Implementación según tu sistema de métricas
    // Ejemplo con Prometheus:
    // httpRequestDuration
    //   .labels(protocol, className, method, status)
    //   .observe(duration / 1000);
  }
}

Casos de Uso: ¿Cuándo Implementar Arquitectura Híbrida en el Mismo Puerto?

Escenarios Ideales

1. E-commerce Platform

Frontend Web/Móvil --REST--> API Gateway (:3000)
                              |
                              +--gRPC--> [Catálogo Service]
                              +--gRPC--> [Pedidos Service]
                              +--gRPC--> [Inventario Service]

Por qué funciona:

– Frontend necesita REST simple para navegadores/móviles

– Comunicación entre servicios requiere alta velocidad (stock en tiempo real)

– Mismo puerto simplifica la infraestructura de despliegue

2. Sistema de Reservas (Booking)

[Frontend] --REST--> [Booking Service :3000 --gRPC--> [Hotels Service]
                          |
                          +--gRPC--> [Flights Service]
                          +--gRPC--> [Payments Service]

Por qué funciona:

– Búsquedas requieren baja latencia entre servicios

– Alta concurrencia en temporadas de viaje

– Contratos estrictos previenen errores en reservas

– Un solo puerto para todo el sistema

3. Fintech / Banking

[Mobile App] --REST--> [API Gateway :3000]
                           |
                           +--gRPC--> [Accounts Service]
                           +--gRPC--> [Transactions Service]
                           +--gRPC--> [Auth Service]

Por qué funciona:

– Seguridad: gRPC usa HTTP/2 con TLS obligatorio

– Performance: transacciones requieren respuestas rápidas

– Consistencia: ProtoBuf asegura contratos inmutables

– Simplificación de la infraestructura de red

Escenarios DONDE NO Usar Híbrido en el Mismo Puerto

> ⚠️ Anti-patrón: Monolito Pequeño

> Si tienes 1-3 endpoints y tráfico bajo (<100 req/s), la complejidad de mantener dos protocolos no justifica el beneficio.

> ⚠️ Anti-patrón: API Pública-Única

> Si tu único cliente es una aplicación web/móvil y no hay comunicación entre microservicios, REST es suficiente.

> ⚠️ Anti-patrón: Equipo Pequeño Sin Experiencia

> Si el equipo no tiene experiencia con gRPC/ProtoBuf, la curva de aprendizaje puede retrasar el desarrollo. Considera migración gradual.

Mejores Prácticas y Anti-Patrones para Microservicios Híbridos

✅ Mejores Prácticas

1. Mantén un único archivo `.proto` como fuente de verdad

# ✅ CORRECTO: Un proto centralizado
# proto/catalog.proto
# Compartido por todos los servicios (product, order, inventory)

2. Usa versionado en tus APIs gRPC

syntax = "proto3";
package catalog.v1; // v1 indica versión

message Product {
  string id = 1;
  // Nunca elimines campos, solo marca como deprecated
  string old_field = 2 [deprecated = true];
  string new_field = 3;
}

3. Implementa health checks para ambos protocolos

// health.controller.ts (REST)
@Controller('api')
export class HealthController {
  @Get('health')
  check() {
    return { status: 'ok', protocol: 'REST', timestamp: new Date() };
  }
}

// health.grpc.controller.ts (gRPC)
@Controller()
export class HealthGrpcController {
  @GrpcMethod('Health', 'Check')
  check() {
    return { status: 'SERVING', protocol: 'gRPC' };
  }
}

4. Usa namespaces y paquetes consistentes

// ✅ CORRECTO: Paquetes consistentes
// .proto
package catalog.products.v1;

// NestJS
@GrpcMethod('catalog.products.v1.ProductsService', 'Get')

❌ Anti-Patrones a Evitar

1. NO mezcles lógica de negocio en controladores

// ❌ INCORRECTO: Lógica en controller
@GrpcMethod('CatalogService', 'CreateProduct')
async createProduct(data: any) {
  const product = { ...data, id: this.generateId() }; // Lógica aquí!
  await this.repo.save(product);
  return product;
}

// ✅ CORRECTO: Delegar a servicio
@GrpcMethod('CatalogService', 'CreateProduct')
async createProduct(data: any) {
  return this.productsService.create(data); // Servicio compartido
}

2. NO ignores errores en clientes gRPC

// ❌ INCORRECTO
const product = await grpcClient.getProduct({ id }).toPromise(); // Sin error handling

// ✅ CORRECTO
try {
  const product = await firstValueFrom(
    grpcClient.getProduct({ id }).pipe(
      timeout(3000),
      catchError(err => {
        if (err.code === Status.NOT_FOUND) {
          throw new ProductNotFoundException(id);
        }
        throw err;
      })
    )
  );
} catch (error) {
  this.logger.error(`Error fetching product: ${error.message}`);
  throw error;
}

3. NO exongas gRPC directamente al internet desde el mismo puerto

// ❌ INCORRECTO: gRPC público accesible
app.connectMicroservice({
  transport: Transport.GRPC,
  options: {
    url: '0.0.0.0:3000', // Accesible desde internet!
  }
});

// ✅ CORRECTO: Solo tráfico interno con firewall
app.connectMicroservice({
  transport: Transport.GRPC,
  options: {
    url: 'localhost:3000', // Solo tráfico interno
  }
});

Testing: Estrategias para Servicios Híbridos

Testing REST (Jest + Supertest)

// products.controller.spec.ts
import { Test, TestingModule } from '@nestjs/testing';
import { INestApplication } from '@nestjs/common';
import * as request from 'supertest';
import { ProductsModule } from './products.module';

describe('ProductsController (REST)', () => {
  let app: INestApplication;

  beforeAll(async () => {
    const moduleFixture: TestingModule = await Test.createTestingModule({
      imports: [ProductsModule],
    }).compile();

    app = moduleFixture.createNestApplication();
    await app.init();
  });

  it('/POST api/products', () => {
    return request(app.getHttpServer())
      .post('/api/products')
      .send({
        name: 'Test Product',
        description: 'Test Description',
        price: 99.99,
        stock: 10,
        category: 'test',
        tags: ['test'],
      })
      .expect(201)
      .expect(res => {
        expect(res.body.data).toHaveProperty('id');
        expect(res.body.data.name).toBe('Test Product');
      });
  });

  afterAll(async () => {
    await app.close();
  });
});

Testing gRPC (Client gRPC)

// products.grpc.controller.spec.ts
import { Test, TestingModule } from '@nestjs/testing';
import { INestApplication } from '@nestjs/common';
import { ClientGrpc, Transport } from '@nestjs/microservices';
import { join } from 'path';
import { ProductsModule } from './products.module';
import * as protobuf from 'protobufjs';

describe('ProductsGrpcController (gRPC)', () => {
  let app: INestApplication;
  let client: any;
  let productService: any;

  beforeAll(async () => {
    const moduleFixture: TestingModule = await Test.createTestingModule({
      imports: [ProductsModule],
    }).compile();

    app = moduleFixture.createNestApplication();

    // Iniciar microservicio gRPC
    app.connectMicroservice({
      transport: Transport.GRPC,
      options: {
        package: 'catalog',
        protoPath: join(__dirname, '../../proto/catalog.proto'),
        url: 'localhost:30001', // Puerto diferente para testing
      },
    });

    await app.startAllMicroservices();
    await app.init();

    // Crear cliente gRPC para testing
    await setupGrpcClient();
  });

  async function setupGrpcClient() {
    const protoPath = join(__dirname, '../../proto/catalog.proto');
    const packageDefinition = protobuf.loadSync(protoPath);
    const catalogProto = packageDefinition.lookup('catalog');
    const CatalogService = catalogProto.lookupService('CatalogService');

    client = CatalogService.create('localhost:30001', grpc.credentials.createInsecure());
    productService = client;
  }

  it('CreateProduct via gRPC', (done) => {
    const request = {
      name: 'Test gRPC Product',
      description: 'Testing gRPC',
      price: 79.99,
      stock: 20,
      category: 'grpc-test',
      tags: ['grpc', 'test'],
    };

    productService.createProduct(request, (err: any, response: any) => {
      expect(err).toBeNull();
      expect(response).toHaveProperty('id');
      expect(response.name).toBe('Test gRPC Product');
      done();
    });
  });

  afterAll(async () => {
    if (client) {
      client.close();
    }
    await app.close();
  });
});

Preguntas Frecuentes (FAQ)

1. ¿Puedo ejecutar REST y gRPC en el mismo puerto?

Respuesta: Sí, pero requiere implementación cuidadosa. REST y gRPC usan diferentes implementaciones de HTTP (REST usa HTTP/1.1 o HTTP/2 con texto, mientras que gRPC requiere HTTP/2 con framing binario específico). NestJS facilita esto con el método `connectMicroservice()` que permite múltiples listeners simultáneos en el mismo puerto.

Alternativa: Puedes implementar un protocol detection middleware que enrute según el tipo de cliente. Esta es la recomendación para arquitecturas modernas que buscan simplificar la infraestructura.

2. ¿Cómo manejo autenticación en ambos protocolos en el mismo puerto?

Respuesta: Para REST, usa JWT en el header `Authorization: Bearer `. Para gRPC, implementa autenticación a nivel de metadata.

Implementación REST:

@Injectable()
export class JwtAuthGuard extends AuthGuard('jwt') {
  canActivate(context: ExecutionContext) {
    return super.canActivate(context);
  }
}

// En controller
@UseGuards(JwtAuthGuard)
@Post('api/products')
createProduct(@Body() dto: CreateProductDto) {
  return this.service.create(dto);
}

Implementación gRPC:

@Injectable()
export class GrpcAuthGuard implements CanActivate {
  canActivate(context: ExecutionContext): boolean {
    const metadata = context.switchToRpc().getContext();
    const authToken = metadata.get('authorization')[0];

    if (!authToken || !authToken.startsWith('Bearer ')) {
      throw new UnauthorizedException();
    }

    // Validar token
    const token = authToken.substring(7);
    try {
      jwt.verify(token, process.env.JWT_SECRET);
      return true;
    } catch {
      return false;
    }
  }
}

// En gRPC controller
@UseGuards(GrpcAuthGuard)
@GrpcMethod('CatalogService', 'CreateProduct')
createProduct(data: any) {
  return this.service.create(data);
}

3. ¿Qué pasa si necesito cambiar un campo en mi archivo .proto?

Respuesta: ProtoBuf está diseñado para evolucionar sin romper clientes existentes. Nunca elimines ni reutilices números de campo. Si necesitas cambiar un campo:

1. Renombrar campo: Solo cambia el nombre en TypeScript, no el número de campo

2. Deprecar campo: Marca como `deprecated` en el .proto

3. Agregar nuevo campo: Usa el siguiente número disponible

4. Cambiar tipo: Solo si es compatible (ej: int32 → int64)

Ejemplo:

message Product {
  string id = 1;
  string name = 2;
  string old_description = 3 [deprecated = true]; // Marcar como deprecated
  string description = 4; // Nuevo campo reemplaza al viejo
  int32 price_in_cents = 5; // Nuevo campo con tipo mejorado
  double price = 6 [deprecated = true]; // Viejo campo deprecated
}

4. ¿Cómo debugueo servicios gRPC en el mismo puerto?

Respuesta: Aunque gRPC usa binario, tienes varias opciones:

Opción 1: grpcurl

# grpcurl permite hacer requests desde línea de comandos
grpcurl -plaintext -d '{"id": "prod_123"}' localhost:3000 catalog.CatalogService/GetProduct

Opción 2: Middleware de logging

// Interceptor que logea todos los mensajes
@Injectable()
export class LoggingInterceptor implements NestInterceptor {
  intercept(context: ExecutionContext, next: CallHandler): Observable<any> {
    const request = context.switchToRpc().getData();
    console.log('gRPC Request:', JSON.stringify(request, null, 2));

    return next.handle().pipe(
      tap(response => {
        console.log('gRPC Response:', JSON.stringify(response, null, 2));
      })
    );
  }
}

5. ¿Cómo deployo microservicios híbridos en producción?

Respuesta: El deployment varía según tu infraestructura, pero aquí hay patrones comunes:

Docker Compose (Desarrollo/Staging):

# docker-compose.yml
version: '3.8'
services:
  catalog-service:
    build: .
    ports:
      - "3000:3000"  # Mismo puerto para REST y gRPC
    environment:
      - NODE_ENV=production
      - PORT=3000
    networks:
      - microservices

  order-service:
    build: ./order-service
    ports:
      - "4000:4000"
    environment:
      - CATALOG_GRPC_URL=localhost:3000
    depends_on:
      - catalog-service
    networks:
      - microservices

networks:
  microservices:
    driver: bridge

Kubernetes (Producción):

# catalog-deployment.yaml
apiVersion: apps/v1
kind: Deployment
metadata:
  name: catalog-service
spec:
  replicas: 3
  selector:
    matchLabels:
      app: catalog-service
  template:
    metadata:
      labels:
        app: catalog-service
    spec:
      containers:
      - name: catalog-service
        image: catalog-service:latest
        ports:
        - containerPort: 3000  # Mismo puerto
          name: http-grpc
        env:
        - name: PORT
          value: "3000"
---
apiVersion: v1
kind: Service
metadata:
  name: catalog-service
spec:
  selector:
    app: catalog-service
  ports:
  - port: 3000
    name: http-grpc
    targetPort: 3000

Takeaways Clave

🎯 Arquitectura Híbrida en el Mismo Puerto = Simplificación + Eficiencia: REST para APIs públicas y clientes web/móviles, gRPC para comunicación interna entre microservicios con máxima eficiencia, todo en un solo puerto.

🎯 Protocol Buffers es 6x más rápido que JSON en serialización/deserialización según benchmarks de Auth0, con payloads 60% más pequeños.

🎯 Middleware de Detección es Crítico: Implementa un protocol detection middleware que enrute correctamente según el tipo de cliente.

🎯 Servicio Compartido es la Base: Mantén la lógica de negocio en un `Service` compartido entre controladores REST y gRPC para evitar duplicación de código.

🎯 Versionado y Backward Compatibility: Usa números de campo secuenciales en archivos `.proto` y marca campos deprecated en lugar de eliminarlos para mantener compatibilidad.

Conclusión

Hemos explorado en profundidad cómo implementar microservicios híbridos con NestJS en el mismo puerto, combinando la accesibilidad universal de REST con la eficiencia superior de gRPC y Protocol Buffers. Esta arquitectura no es una moda pasajera, sino una solución madura adoptada por empresas como Google, Netflix, y Microsoft para sistemas de misión crítica.

El punto clave es que no debes elegir entre REST y gRPC; puedes usar ambos según el contexto en el mismo puerto. Tu frontend web se comunica via REST simple, mientras que tus microservicios internos (pedidos, inventario, pagos) se comunican via gRPC con latencias 6x menores y payloads 60% más compactos, todo compartiendo la misma infraestructura de red.

Al implementar esta arquitectura en el mismo puerto, logras:

Simplificación de infraestructura: Un solo puerto para monitorear, configurar y asegurar

Performance: 6x más rápido en comunicación entre servicios

Mantenibilidad: Una sola base de código con lógica compartida

Compatibilidad: REST para cualquier cliente, gRPC para servicios optimizados

Escalabilidad: Scale independiente de cada capa según carga

Próximos pasos:

1. Clona el repositorio de ejemplo y ejecuta el servicio híbrido localmente

2. Prueba crear productos via REST y consultarlos via gRPC

3. Mide latencia con herramientas como autocannon (REST) y ghz (gRPC)

4. Implementa un segundo microservicio (ej: Order Service) que consuma Catalog Service via gRPC

5. Deploy en Docker Compose o Kubernetes para ver el sistema completo en acción

¿Listo para llevar tu arquitectura de microservicios al siguiente nivel? Comienza implementando tu primer servicio híbrido en el mismo puerto hoy mismo y comparte tu experiencia con la comunidad.

Recursos Adicionales

Documentación Oficial

NestJS Microservices Documentation – Guía completa de microservicios

NestJS gRPC Transport – Documentación específica de gRPC

Protocol Buffers Official Docs – Documentación de Google ProtoBuf v3

gRPC Official Documentation – Guías y tutoriales oficiales

Herramientas de Desarrollo

BloomRPC – GUI client para gRPC

grpcurl – cURL-like para gRPC

Postman gRPC Support – Testing gRPC desde Postman

ghz – Benchmarking tool para gRPC

Artículos y Tutoriales

NestJS + gRPC: Step by Step Guide – Tutorial completo con e2e tests

Beating JSON Performance with Protobuf – Benchmarks y análisis de rendimiento

Microservices Communication with gRPC – Patrones de comunicación

Protobuf vs JSON: Performance, Efficiency & API Speed – Comparativa detallada 2025

Repositorios GitHub

NestJS Microservices Examples – Ejemplos oficiales

NestJS gRPC Example – Ejemplo específico de gRPC

ProtoBuf TypeScript Generator – Generador de TypeScript desde .proto

Librerías y Paquetes npm

@nestjs/microservices – Paquete oficial de NestJS

@grpc/grpc-js – Cliente gRPC puro para Node.js

@grpc/proto-loader – Loader dinámico de archivos .proto

ts-proto – Generador de TypeScript types desde .proto

opossum – Circuit Breaker pattern implementation

Ruta de Aprendizaje: Siguientes Pasos

Una vez que domines los microservicios híbridos básicos con NestJS en el mismo puerto, estos son los temas avanzados recomendados para continuar tu crecimiento:

1. **Service Mesh con Istio o Linkerd**

Por qué es el siguiente lógico: Cuando tienes múltiples microservicios comunicándose via gRPC, necesitas herramientas para manejar mTLS, observabilidad, traffic shifting, y circuit breaking de forma centralizada. Service Mesh como Istio se integra perfectamente con gRPC y NestJS.

Qué aprenderás:

– Configurar Istio en Kubernetes con servicios gRPC

– Implementar mTLS automático entre servicios

– Traffic splitting para canary deployments

– Distributed tracing con Jaeger/Zipkin

– Observabilidad con Prometheus + Grafana

2. **Event-Driven Architecture con Event Sourcing y CQRS**

Por qué es el siguiente lógico: Los microservicios híbridos usan comunicación síncrona (request/response). El siguiente nivel es arquitecturas asíncronas basadas en eventos donde los servicios se comunican via message brokers (Kafka, RabbitMQ, NATS).

Qué aprenderás:

– Patrones Event Sourcing para mantener auditoría completa

– CQRS (Command Query Responsibility Segregation) para separar lecturas de escrituras

– Implementación con NestJS + Kafka/RabbitMQ

– SAGA pattern para transacciones distribuidas

– Eventual consistency y cómo manejarlo

Challenge Práctico: Construye un E-commerce Híbrido Complejo

Objetivo

Implementar un sistema de e-commerce con microservicios híbridos que demuestre comunicación REST y gRPC en el mismo puerto en un escenario realista.

Requisitos Mínimos

1. Catálogo de Productos (Híbrido en Puerto 3000)

– ✅ Exponer REST API para frontend en `/api`

– ✅ Exponer gRPC API para comunicación interna

– ✅ Implementar paginación, filtrado por categoría

– ✅ Búsqueda full-text de productos

– ✅ Validación con `class-validator` en REST, ProtoBuf en gRPC

2. Servicio de Pedidos (Consumidor gRPC)

– ✅ REST API para crear pedidos desde frontend

– ✅ Al crear pedido, llamar a Catálogo via gRPC para:

– Validar existencia de productos

– Verificar stock disponible

– Reducir stock del producto

– ✅ Implementar reintentos con circuit breaker si Catálogo no responde

3. Servicio de Notificaciones (Pub/Sub)

– ✅ Publicar evento cuando se crea un pedido

– ✅ Servicio de notificaciones se suscribe y envía email

– ✅ Usar NestJS EventEmitter o RabbitMQ/Kafka

4. Testing

– ✅ Unit tests de servicios (Jest)

– ✅ Integration tests de APIs REST (Supertest)

– ✅ E2E tests que verifiquen: Pedido → Validación gRPC → Notificación

5. Dockerización

– ✅ Dockerfile para cada servicio

– ✅ docker-compose.yml para orquestar todo el sistema

– ✅ Redes Docker para comunicación entre contenedores

Tiempo Estimado

Setup inicial: 1 hora

Implementación Catálogo Service: 3-4 horas

Implementación Order Service: 2-3 horas

Implementación Notification Service: 1-2 horas

Testing: 2-3 horas

Dockerización: 1-2 horas

Total: 10-15 horas (desarrollo completo)

Checklist de Validación

Proyecto Completo Cuando:
[ ] Catálogo Service escucha en :3000 con REST y gRPC
[ ] Order Service consume Catálogo via gRPC
[ ] Pedido creado → Stock reducido (data consistency)
[ ] Notificación enviada al crear pedido
[ ] Todos los tests pasan (>80% coverage)
[ ] docker-compose up levanta todo el sistema
[ ] README con instrucciones de instalación y uso
[ ] Arquitectura documentada (diagrama o README)

¡Buena suerte con tu implementación! Cuando termines, compártelo con la comunidad de NestJS y gRPC para recibir feedback. Este challenge pondrá a prueba todos los conceptos aprendidos en este artículo y te dará un portfolio project sólido.

Última actualización: Enero 2026

Versión: 1.0

Autor: Basado en documentación oficial de NestJS y mejores prácticas de la industria

¿Encontraste este artículo útil? Compártelo con tu equipo y ayuda a la comunidad a construir arquitecturas de microservicios más eficientes y escalables.

Deja un comentario

Scroll al inicio

Discover more from Creapolis

Subscribe now to keep reading and get access to the full archive.

Continue reading