profile

Ariel E. Castro Cavadia 👋🏾

Un apasionado por el Desarrollo Full Stack, Arquitecto Cloud y especialista en Inteligencia de Negocios (BI) y Transformación Digital. Construyo aplicaciones móviles para Android e iOS, plataformas web robustas, y lidero la adopción de nuevas tecnologías en entornos corporativos.A Passionate Full Stack Developer, Cloud Architect & Business Intelligence (BI) & Digital Transformation specialist. I build mobile apps for Android & iOS, robust web platforms, and lead technology adoption in corporate environments.

Desarrollo Development

Cómo escalar APIs de alto rendimiento con Node.js y NestJS How to Scale High-Performance APIs with Node.js & NestJS

  • Lectura de 8 min 8 min read
  • 6 de Mayo, 2026 May 6, 2026
  • Ariel E. Castro Cavadia
Escalar APIs con Node.js y NestJS

Node.js es reconocido a nivel mundial por su modelo de E/S no bloqueante basado en eventos, ejecutándose sobre un único hilo de ejecución. Si bien esta arquitectura es fantástica para aplicaciones con alta densidad de operaciones de red (I/O-intensive), cuando nos enfrentamos al reto de sostener millones de peticiones por segundo y procesar transacciones pesadas en APIs de nivel corporativo, el diseño de arquitectura y la optimización del stack juegan un papel crítico.

Como especialista en desarrollo backend y arquitecto cloud, he diseñado y escalado decenas de plataformas de alta concurrencia. En este artículo profundizaremos en el conjunto de estrategias, patrones de código y buenas prácticas clave para llevar tus APIs construidas con Node.js y NestJS al siguiente nivel de rendimiento y estabilidad.

1. Optimizando el Núcleo: El motor Fastify

Por defecto, NestJS viene configurado con Express bajo el capó. Express es, sin duda, el estándar de facto de la industria por su madurez y ecosistema. Sin embargo, cuando la prioridad absoluta es la velocidad de respuesta y la baja latencia, Express introduce cierto overhead innecesario.

Migrar a Fastify como adaptador del motor HTTP en NestJS es una de las victorias rápidas más impactantes. Fastify es hasta 2 veces más rápido que Express gracias al uso interno de compilación rápida de esquemas JSON (mediante librerías como fast-json-stringify) y un enrutador altamente optimizado (Find-My-Way).

La implementación en NestJS es sumamente sencilla y transparente:

// main.ts
import { NestFactory } from '@nestjs/core';
import { FastifyAdapter, NestFastifyApplication } from '@nestjs/platform-fastify';
import { AppModule } from './app.module';

async function bootstrap() {
  const app = await NestFactory.create<NestFastifyApplication>(
    AppModule,
    new FastifyAdapter({ 
      logger: true,
      keepAliveTimeout: 65000 // Optimización de persistencia de conexión
    })
  );
  
  await app.listen(3000, '0.0.0.0');
}
bootstrap();

Nota de Arquitectura: Asegúrate de que los middlewares o librerías que utilices en tu proyecto sean compatibles con Fastify, o utiliza los wrappers correspondientes que provee la comunidad de NestJS.

2. Procesamiento Multinúcleo y Clustering Activo

Por diseño, Node.js se ejecuta en un único núcleo de la CPU. En entornos de producción modernos, los servidores cloud (AWS EC2, ECS, etc.) disponen de múltiples núcleos que quedan completamente desaprovechados si corremos una sola instancia estándar de Node.js.

Para resolver esto, debemos implementar clustering de procesos. Esto nos permite instanciar un proceso "Worker" independiente por cada núcleo disponible en el procesador. La herramienta estándar de la industria para gestionar esto en entornos productivos es PM2 en modo cluster.

Podemos definir un archivo de configuración premium ecosystem.config.json para automatizar el escalamiento de forma inteligente:

{
  "apps": [
    {
      "name": "nestjs-high-perf-api",
      "script": "dist/main.js",
      "instances": "max",
      "exec_mode": "cluster",
      "env_production": {
        "NODE_ENV": "production"
      },
      "max_memory_restart": "1G",
      "kill_timeout": 3000 // Apagado elegante de workers
    }
  ]
}

Al definir "instances": "max", PM2 detectará automáticamente la cantidad de núcleos físicos disponibles del servidor y distribuirá las peticiones HTTP entrantes mediante balanceo de carga interno (Round-Robin), multiplicando el rendimiento general de forma transparente.

3. Estrategias de Caching Estratégico (Redis + In-Memory)

La regla de oro de la escalabilidad es simple: evita golpear la base de datos a menos que sea estrictamente necesario. La base de datos suele ser el cuello de botella primario de cualquier sistema.

Para flujos con alta concurrencia de lectura (como catálogos de servicios, perfiles o listados públicos), implementamos una estrategia de caching multinivel:

  • L1 (In-Memory Cache): Ubicada directamente en la memoria del proceso (usando el módulo de NestJS cache-manager). Tiene tiempos de respuesta instantáneos (<1ms) y es ideal para datos estáticos globales.
  • L2 (Redis Cache): Una base de datos en memoria externa distribuida. Ideal para datos compartidos entre múltiples pods o instancias cloud, con persistencia y TTL (Time-To-Live) dinámicos.

Configuración del módulo de Caché dinámico en NestJS:

// cache.module.ts
import { Module } from '@nestjs/common';
import { CacheModule } from '@nestjs/cache-manager';
import * as redisStore from 'cache-manager-redis-store';

@Module({
  imports: [
    CacheModule.register({
      isGlobal: true,
      store: redisStore,
      host: process.env.REDIS_HOST || 'localhost',
      port: 6379,
      ttl: 600, // 10 minutos por defecto
    }),
  ],
})
export class AppCacheModule {}

4. Optimización del Database Connection Pooling

Cuando escalamos instancias de API mediante contenedores (como en Kubernetes o AWS ECS), cada contenedor abre y mantiene conexiones activas hacia la base de datos. Si no limitamos y configuramos correctamente el Connection Pool, colapsaremos la base de datos rápidamente con un error de Too many connections.

Si utilizas un ORM moderno como Prisma o TypeORM, debes configurar finamente las opciones de pooling. Por ejemplo, en Prisma, ajustamos los parámetros de la URL de conexión:

DATABASE_URL="postgresql://user:password@localhost:5432/mydb?connection_limit=20&pool_timeout=10"

Al establecer un límite máximo saludable de conexiones por contenedor (e.g., 20), aseguramos una alta reutilización de hilos de conexión sin agotar el límite global del motor de base de datos relacional (como PostgreSQL).

5. Desacoplamiento de Procesos Costosos mediante Colas Asíncronas (BullMQ)

El hilo principal de Node.js nunca debe ser bloqueado por operaciones que no requieren una respuesta HTTP inmediata. Procesamientos costosos como:

  • Envío de correos transaccionales masivos.
  • Generación de reportes dinámicos o PDFs.
  • Consumo de APIs externas lentas (Webhooks).

Deben ser manejados de forma asíncrona mediante una arquitectura basada en eventos utilizando colas de mensajería fiables como BullMQ con Redis.

El cliente realiza la petición, la API encola la tarea en Redis y devuelve una respuesta inmediata de 202 Accepted. Un worker independiente en segundo plano procesa la cola sin afectar en lo absoluto el tiempo de respuesta del usuario final.

Ejemplo de productor de cola en NestJS:

// orders.service.ts
import { Injectable } from '@nestjs/common';
import { InjectQueue } from '@nestjs/bull';
import { Queue } from 'bull';

@Injectable()
export class OrdersService {
  constructor(@InjectQueue('order-processing') private orderQueue: Queue) {}

  async createOrder(orderDto: any) {
    // 1. Guardar orden en BD local de forma rápida
    const order = { id: 'ORD-2026', ...orderDto };
    
    // 2. Encolar procesamiento pesado asíncronamente
    await this.orderQueue.add('processInvoice', {
      orderId: order.id,
      email: orderDto.customerEmail
    }, {
      attempts: 3, // Reintentar si falla
      backoff: 5000 // Esperar 5s antes de reintentar
    });

    return { success: true, message: 'Order received', orderId: order.id };
  }
}

Conclusión

Escalar APIs de Node.js y NestJS no se trata de agregar más memoria y CPU sin control. Se trata de tomar decisiones arquitectónicas inteligentes: migrar a un motor HTTP ultrarrápido como Fastify, exprimir el paralelismo del procesador con clustering, reducir la latencia con Redis y desacoplar tareas con colas distribuidas.

Al implementar estas cinco estrategias en conjunto, tus microservicios e infraestructura Backend estarán listos para manejar cargas masivas con latencias consistentemente bajas, garantizando una experiencia de usuario premium.

Node.js is globally recognized for its event-driven, non-blocking I/O model running on a single thread. While this architecture is fantastic for applications with high network density (I/O-intensive), when we face the challenge of sustaining millions of requests per second and processing heavy transactions in enterprise-grade APIs, architectural design and stack optimization play a critical role.

As a backend development specialist and cloud architect, I have designed and scaled dozens of highly concurrent platforms. In this article, we will delve deep into the key set of strategies, code patterns, and best practices to take your APIs built with Node.js and NestJS to the next level of performance and stability.

1. Optimizing the Core: The Fastify Engine

By default, NestJS comes configured with Express under the hood. Express is undoubtedly the industry de facto standard because of its maturity and rich ecosystem. However, when absolute response speed and ultra-low latency are the top priorities, Express introduces some unnecessary overhead.

Migrating to Fastify as the HTTP engine adapter in NestJS is one of the most impactful quick wins. Fastify is up to 2 times faster than Express thanks to its internal use of fast JSON schema compilation (via libraries like fast-json-stringify) and a highly optimized router (Find-My-Way).

Implementing it in NestJS is extremely simple and seamless:

// main.ts
import { NestFactory } from '@nestjs/core';
import { FastifyAdapter, NestFastifyApplication } from '@nestjs/platform-fastify';
import { AppModule } from './app.module';

async function bootstrap() {
  const app = await NestFactory.create<NestFastifyApplication>(
    AppModule,
    new FastifyAdapter({ 
      logger: true,
      keepAliveTimeout: 65000 // Connection persistence optimization
    })
  );
  
  await app.listen(3000, '0.0.0.0');
}
bootstrap();

Architectural Note: Make sure that the middlewares or libraries you use in your project are compatible with Fastify, or use the corresponding wrappers provided by the NestJS community.

2. Multi-Core Processing and Active Clustering

By design, Node.js runs on a single CPU core. In modern production environments, cloud servers (AWS EC2, ECS, etc.) feature multiple cores that go completely wasted if we run a single standard Node.js instance.

To solve this, we must implement process clustering. This allows us to instantiate an independent "Worker" process for each available core in the CPU. The industry-standard tool to manage this in production environments is PM2 in cluster mode.

We can define a premium configuration file ecosystem.config.json to automate scaling intelligently:

{
  "apps": [
    {
      "name": "nestjs-high-perf-api",
      "script": "dist/main.js",
      "instances": "max",
      "exec_mode": "cluster",
      "env_production": {
        "NODE_ENV": "production"
      },
      "max_memory_restart": "1G",
      "kill_timeout": 3000 // Graceful shutdown of workers
    }
  ]
}

By defining "instances": "max", PM2 will automatically detect the number of physical cores available on the server and distribute incoming HTTP requests via internal load balancing (Round-Robin), multiplying overall throughput transparently.

3. Strategic Caching Strategies (Redis + In-Memory)

The golden rule of scalability is simple: avoid hitting the database unless absolutely necessary. The database is usually the primary bottleneck of any system.

For high-concurrency read operations (like service catalogs, profiles, or public lists), we implement a multi-level caching strategy:

  • L1 (In-Memory Cache): Placed directly in the process memory (using NestJS's cache-manager module). It features instant response times (<1ms) and is ideal for global static data.
  • L2 (Redis Cache): A distributed external in-memory database. Ideal for data shared across multiple pods or cloud instances, with dynamic persistence and TTL (Time-To-Live).

Configuring the dynamic Cache module in NestJS:

// cache.module.ts
import { Module } from '@nestjs/common';
import { CacheModule } from '@nestjs/cache-manager';
import * as redisStore from 'cache-manager-redis-store';

@Module({
  imports: [
    CacheModule.register({
      isGlobal: true,
      store: redisStore,
      host: process.env.REDIS_HOST || 'localhost',
      port: 6379,
      ttl: 600, // 10 minutes by default
    }),
  ],
})
export class AppCacheModule {}

4. Database Connection Pooling Optimization

When scaling API instances using containers (such as in Kubernetes or AWS ECS), each container opens and maintains active connections to the database. If we do not limit and correctly configure the Connection Pool, we will quickly crash the database with a Too many connections error.

If you use a modern ORM like Prisma or TypeORM, you must finely tune pooling options. For example, in Prisma, we adjust the connection URL parameters:

DATABASE_URL="postgresql://user:password@localhost:5432/mydb?connection_limit=20&pool_timeout=10"

By establishing a healthy maximum connection limit per container (e.g., 20), we ensure high connection thread reuse without exhausting the global limit of the relational database engine (such as PostgreSQL).

5. Decoupling Expensive Processes via Asynchronous Queues (BullMQ)

The Node.js main thread should never be blocked by operations that do not require an immediate HTTP response. Heavy tasks such as:

  • Sending mass transactional emails.
  • Generating dynamic reports or PDFs.
  • Consuming slow external APIs (Webhooks).

Must be handled asynchronously using an event-driven architecture with reliable message queues like BullMQ with Redis.

The client makes the request, the API queues the task in Redis and returns an immediate 202 Accepted response. An independent background worker processes the queue without affecting the end-user's response time at all.

Example of a queue producer in NestJS:

// orders.service.ts
import { Injectable } from '@nestjs/common';
import { InjectQueue } from '@nestjs/bull';
import { Queue } from 'bull';

@Injectable()
export class OrdersService {
  constructor(@InjectQueue('order-processing') private orderQueue: Queue) {}

  async createOrder(orderDto: any) {
    // 1. Quick save order to local database
    const order = { id: 'ORD-2026', ...orderDto };
    
    // 2. Queue heavy processing asynchronously
    await this.orderQueue.add('processInvoice', {
      orderId: order.id,
      email: orderDto.customerEmail
    }, {
      attempts: 3, // Retry if it fails
      backoff: 5000 // Wait 5s before retrying
    });

    return { success: true, message: 'Order received', orderId: order.id };
  }
}

Conclusion

Scaling Node.js and NestJS APIs is not about throwing memory and CPU at the server without a plan. It's about making smart architectural decisions: migrating to an ultra-fast HTTP engine like Fastify, harnessing processor parallelism with clustering, reducing latency with Redis, and decoupling tasks with distributed queues.

By implementing these five strategies together, your microservices and Backend infrastructure will be ready to handle massive loads with consistently low latencies, ensuring a premium user experience.

banner-shape-1
banner-shape-1
object-3d-1
object-3d-2