NestJS y Redis en 2026: Caché, Sesiones y Preguntas de Entrevista

Guía completa sobre la integración de Redis con NestJS: configuración del cache-manager, gestión de sesiones distribuidas y preguntas técnicas para entrevistas de desarrollador.

NestJS y Redis: Caché y gestión de sesiones

La integración de Redis con NestJS transforma el rendimiento de las aplicaciones al reemplazar las consultas a base de datos con búsquedas en memoria. La combinación de @nestjs/cache-manager para caché y express-session con connect-redis para persistencia de sesiones cubre los dos casos de uso de Redis más comunes en aplicaciones backend.

Concepto Clave

El caché de Redis en NestJS reduce la carga en la base de datos almacenando datos frecuentemente consultados en memoria. La gestión de sesiones con Redis permite el escalado horizontal al compartir el estado de sesiones entre múltiples instancias.

Configuración de @nestjs/cache-manager con Redis

El módulo cache-manager se trasladó a un paquete separado en NestJS 10. Su instalación requiere tanto el wrapper de NestJS como el store de caché subyacente. El adaptador @keyv/redis es el enfoque recomendado para la integración de Redis con cache-manager v5.

bash
# Instalación de paquetes requeridos
npm install @nestjs/cache-manager cache-manager @keyv/redis

El registro del módulo configura la conexión a Redis y el TTL predeterminado. La opción isGlobal hace que el caché esté disponible en todos los módulos sin necesidad de reimportarlo.

app.module.tstypescript
import { Module } from '@nestjs/common';
import { CacheModule } from '@nestjs/cache-manager';
import KeyvRedis from '@keyv/redis';

@Module({
  imports: [
    CacheModule.registerAsync({
      isGlobal: true,
      useFactory: () => ({
        stores: [
          new KeyvRedis(process.env.REDIS_URL || 'redis://localhost:6379'),
        ],
        ttl: 60000, // TTL predeterminado en milisegundos
      }),
    }),
  ],
})
export class AppModule {}

El array stores acepta múltiples stores de caché para caching multinivel. Los entornos de producción típicamente usan Redis como store principal, con un fallback opcional en memoria para desarrollo.

Inyección del Caché y Caching a Nivel de Servicio

El token CACHE_MANAGER proporciona acceso directo al caché para operaciones a nivel de servicio. Este enfoque ofrece más control que el caching HTTP automático cuando la lógica de negocio dicta la invalidación del caché.

users.service.tstypescript
import { Injectable, Inject } from '@nestjs/common';
import { CACHE_MANAGER, Cache } from '@nestjs/cache-manager';
import { UsersRepository } from './users.repository';
import { User } from './user.entity';

@Injectable()
export class UsersService {
  constructor(
    @Inject(CACHE_MANAGER) private cache: Cache,
    private usersRepository: UsersRepository,
  ) {}

  async findById(id: string): Promise<User | null> {
    // Verificar caché primero
    const cacheKey = `user:${id}`;
    const cached = await this.cache.get<User>(cacheKey);
    
    if (cached) {
      return cached;
    }

    // Cache miss: obtener de la base de datos
    const user = await this.usersRepository.findById(id);
    
    if (user) {
      await this.cache.set(cacheKey, user, 300000); // TTL de 5 minutos
    }

    return user;
  }

  async update(id: string, data: Partial<User>): Promise<User> {
    const user = await this.usersRepository.update(id, data);
    
    // Invalidar caché después de la actualización
    await this.cache.del(`user:${id}`);
    
    return user;
  }
}

El patrón cache-aside implementado arriba verifica el caché antes de consultar la base de datos. Las operaciones de escritura invalidan la entrada del caché para garantizar la consistencia de los datos.

Decorador CacheInterceptor para Caching HTTP

El CacheInterceptor proporciona caching automático de respuestas para endpoints REST. Utiliza la URL de la petición como clave de caché por defecto, lo que lo hace ideal para endpoints GET que sirven datos estáticos o que cambian con poca frecuencia.

products.controller.tstypescript
import { Controller, Get, UseInterceptors } from '@nestjs/common';
import { CacheInterceptor, CacheTTL, CacheKey } from '@nestjs/cache-manager';
import { ProductsService } from './products.service';

@Controller('products')
@UseInterceptors(CacheInterceptor)
export class ProductsController {
  constructor(private productsService: ProductsService) {}

  @Get()
  @CacheTTL(120000) // Override TTL: 2 minutos
  async findAll() {
    return this.productsService.findAll();
  }

  @Get('featured')
  @CacheKey('products:featured')
  @CacheTTL(300000) // 5 minutos
  async findFeatured() {
    return this.productsService.findFeatured();
  }
}

El decorador @CacheTTL() sobrescribe el TTL predeterminado para rutas específicas. El decorador @CacheKey() permite claves de caché personalizadas en lugar del esquema predeterminado basado en URL.

Gestión de Sesiones con Redis

La gestión de sesiones requiere express-session con el store de Redis. Esta configuración habilita el almacenamiento persistente de sesiones que sobrevive a los reinicios del servidor y funciona con múltiples instancias de la aplicación.

bash
# Instalación de dependencias de sesión
npm install express-session connect-redis ioredis
npm install -D @types/express-session

La configuración de sesión se realiza en el archivo main.ts antes de la inicialización de la aplicación NestJS.

main.tstypescript
import { NestFactory } from '@nestjs/core';
import { AppModule } from './app.module';
import * as session from 'express-session';
import RedisStore from 'connect-redis';
import { Redis } from 'ioredis';

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

  const redisClient = new Redis(process.env.REDIS_URL || 'redis://localhost:6379');

  app.use(
    session({
      store: new RedisStore({ client: redisClient }),
      secret: process.env.SESSION_SECRET || 'change-this-secret',
      resave: false,
      saveUninitialized: false,
      cookie: {
        secure: process.env.NODE_ENV === 'production',
        httpOnly: true,
        maxAge: 24 * 60 * 60 * 1000, // 24 horas
        sameSite: 'lax',
      },
    }),
  );

  await app.listen(3000);
}
bootstrap();

La opción resave: false evita operaciones de guardado innecesarias. La opción saveUninitialized: false previene la creación de sesiones vacías, lo cual es importante para el cumplimiento del GDPR.

Acceso a Sesiones en Controladores

El objeto de sesión está disponible a través del objeto request de Express. Un decorador personalizado simplifica el acceso a sesiones en los handlers de ruta.

session.decorator.tstypescript
import { createParamDecorator, ExecutionContext } from '@nestjs/common';
import { Request } from 'express';

export const Session = createParamDecorator(
  (data: string | undefined, ctx: ExecutionContext) => {
    const request = ctx.switchToHttp().getRequest<Request>();
    const session = request.session;
    
    return data ? session?.[data] : session;
  },
);

// auth.controller.ts
import { Controller, Post, Body, Get } from '@nestjs/common';
import { Session } from './session.decorator';
import { AuthService } from './auth.service';

@Controller('auth')
export class AuthController {
  constructor(private authService: AuthService) {}

  @Post('login')
  async login(
    @Body() credentials: LoginDto,
    @Session() session: Record<string, any>,
  ) {
    const user = await this.authService.validateUser(credentials);
    
    if (user) {
      session.userId = user.id;
      session.loginAt = new Date().toISOString();
      return { success: true };
    }
    
    return { success: false };
  }

  @Post('logout')
  async logout(@Session() session: Record<string, any>) {
    return new Promise((resolve) => {
      session.destroy((err: Error | null) => {
        resolve({ success: !err });
      });
    });
  }

  @Get('profile')
  async getProfile(@Session('userId') userId: string) {
    if (!userId) {
      return { authenticated: false };
    }
    
    return this.authService.getUserProfile(userId);
  }
}

Patrones Avanzados de Caching

Las aplicaciones de producción frecuentemente requieren estrategias de caching más sofisticadas. El patrón write-through actualiza el caché simultáneamente con la base de datos, mientras que el patrón cache-aside carga datos bajo demanda.

orders.service.tstypescript
import { Injectable, Inject } from '@nestjs/common';
import { CACHE_MANAGER, Cache } from '@nestjs/cache-manager';
import { OrdersRepository } from './orders.repository';
import { Order } from './order.entity';

@Injectable()
export class OrdersService {
  constructor(
    @Inject(CACHE_MANAGER) private cache: Cache,
    private ordersRepository: OrdersRepository,
  ) {}

  // Patrón Write-Through
  async create(orderData: CreateOrderDto): Promise<Order> {
    const order = await this.ordersRepository.create(orderData);
    
    // Escribir en caché inmediatamente
    await this.cache.set(`order:${order.id}`, order, 600000);
    
    // Invalidar cachés de lista
    await this.invalidateListCaches(order.userId);
    
    return order;
  }

  // Patrón de prefijos de claves de caché
  async findByUserId(userId: string): Promise<Order[]> {
    const cacheKey = `orders:user:${userId}`;
    const cached = await this.cache.get<Order[]>(cacheKey);
    
    if (cached) {
      return cached;
    }

    const orders = await this.ordersRepository.findByUserId(userId);
    await this.cache.set(cacheKey, orders, 300000);
    
    return orders;
  }

  private async invalidateListCaches(userId: string): Promise<void> {
    await this.cache.del(`orders:user:${userId}`);
    await this.cache.del('orders:recent');
  }
}

Gestión de Conexión Redis y Health Checks

Las aplicaciones de producción requieren monitoreo de conexiones Redis y endpoints de health check.

redis-health.indicator.tstypescript
import { Injectable } from '@nestjs/common';
import { HealthIndicator, HealthIndicatorResult, HealthCheckError } from '@nestjs/terminus';
import { Redis } from 'ioredis';

@Injectable()
export class RedisHealthIndicator extends HealthIndicator {
  constructor(private redis: Redis) {
    super();
  }

  async isHealthy(key: string): Promise<HealthIndicatorResult> {
    try {
      const start = Date.now();
      await this.redis.ping();
      const latency = Date.now() - start;

      return this.getStatus(key, true, { latency: `${latency}ms` });
    } catch (error) {
      throw new HealthCheckError(
        'Redis health check failed',
        this.getStatus(key, false, { error: error.message }),
      );
    }
  }
}

¿Listo para aprobar tus entrevistas de Node.js / NestJS?

Practica con nuestros simuladores interactivos, flashcards y tests técnicos.

Preguntas de Entrevista Comunes sobre Caching con Redis

Las entrevistas técnicas evalúan frecuentemente la comprensión de patrones de caching y sus compromisos. Estas preguntas cubren conceptos fundamentales y escenarios reales de producción.

P: ¿Cuál es la diferencia entre los patrones cache-aside y write-through?

El patrón cache-aside carga datos en el caché solo cuando se accede a ellos, lo que reduce el uso de memoria del caché pero puede crear cache misses iniciales. El patrón write-through actualiza el caché durante las operaciones de escritura, garantizando que el caché siempre contenga datos frescos pero aumentando la latencia de escritura.

P: ¿Cómo manejar la invalidación del caché en un sistema distribuido?

Los enfoques incluyen: invalidación basada en eventos usando Redis Pub/Sub, TTL con refrescos eventualmente consistentes, y estrategias de versionado de claves de caché. La elección depende de los requisitos de consistencia y la latencia aceptable.

P: ¿Cuándo usar caching a nivel de sesión vs caching a nivel de aplicación?

El caché a nivel de sesión almacena datos específicos del usuario como preferencias o contenido del carrito. El caché a nivel de aplicación almacena datos compartidos como catálogos de productos o configuraciones. Combinar ambos optimiza el uso de recursos.

P: ¿Cómo implementaría un caché distribuido con invalidación?

distributed-cache.service.tstypescript
import { Injectable, Inject } from '@nestjs/common';
import { CACHE_MANAGER, Cache } from '@nestjs/cache-manager';
import { Redis } from 'ioredis';

@Injectable()
export class DistributedCacheService {
  private subscriber: Redis;

  constructor(
    @Inject(CACHE_MANAGER) private cache: Cache,
    private redis: Redis,
  ) {
    this.subscriber = redis.duplicate();
    this.setupSubscriber();
  }

  private setupSubscriber(): void {
    this.subscriber.subscribe('cache:invalidate');
    
    this.subscriber.on('message', async (channel, message) => {
      if (channel === 'cache:invalidate') {
        const { pattern } = JSON.parse(message);
        await this.invalidatePattern(pattern);
      }
    });
  }

  async invalidateAcrossInstances(pattern: string): Promise<void> {
    await this.redis.publish(
      'cache:invalidate',
      JSON.stringify({ pattern }),
    );
  }

  private async invalidatePattern(pattern: string): Promise<void> {
    // La implementación depende del cache store
    // Para Redis, usar SCAN con pattern matching
    const keys = await this.redis.keys(pattern);
    if (keys.length > 0) {
      await this.redis.del(...keys);
    }
  }
}

P: ¿Cómo prevenir cache stampedes?

Los cache stampedes ocurren cuando múltiples solicitudes intentan simultáneamente regenerar una entrada de caché expirada. Las soluciones incluyen probabilistic early recomputation, bloqueo con semáforos, o distribución de la expiración con TTLs aleatorios.

Buenas Prácticas de Producción

El despliegue de aplicaciones NestJS basadas en Redis en producción requiere atención especial a la gestión de conexiones y estrategias de failover.

redis.module.tstypescript
import { Module, Global } from '@nestjs/common';
import { Redis } from 'ioredis';

@Global()
@Module({
  providers: [
    {
      provide: 'REDIS_CLIENT',
      useFactory: () => {
        const redis = new Redis(process.env.REDIS_URL, {
          maxRetriesPerRequest: 3,
          retryDelayOnFailover: 100,
          retryDelayOnClusterDown: 100,
          enableReadyCheck: true,
          lazyConnect: true,
        });

        redis.on('error', (err) => {
          console.error('Redis connection error:', err);
        });

        redis.on('connect', () => {
          console.log('Redis connected');
        });

        return redis;
      },
    },
  ],
  exports: ['REDIS_CLIENT'],
})
export class RedisModule {}

El parámetro maxRetriesPerRequest evita que las solicitudes se bloqueen indefinidamente durante fallos de Redis. La opción lazyConnect retrasa la conexión hasta la primera operación, mejorando los tiempos de inicio.

Conclusión

La integración de Redis con NestJS ofrece una solución robusta para caching y gestión de sesiones. La combinación del cache-manager para caché de aplicación y connect-redis para sesiones cubre la mayoría de los casos de uso en producción. Dominar estos patrones es esencial para cualquier desarrollador NestJS que trabaje en aplicaciones de alto tráfico.

Reto diario

¿Sabrías detectar el bug en Node.js / NestJS?

Un fragmento real, un bug oculto, un intento al día. Sin cuenta para probar.

Anthony Fillion-Maillet

Escrito por

Anthony Fillion-Maillet

Fundador de SharpSkill

Desarrollador fullstack desde hace más de 10 años. Dirige SharpSkill y responde por todo lo que se publica aquí.

Actualizado el 10 de septiembre de 2026

Etiquetas

#nestjs
#redis
#caching
#sesiones
#node.js

Compartir

Artículos relacionados