NestJS e Redis em 2026: Cache, Sessões e Perguntas de Entrevista

Guia completo sobre a integração do Redis com NestJS: configuração do cache-manager, gerenciamento de sessões distribuídas e perguntas técnicas para entrevistas de desenvolvedor.

NestJS e Redis: Cache e gerenciamento de sessões

A integração do Redis com NestJS transforma o desempenho das aplicações ao substituir consultas ao banco de dados por buscas em memória. A combinação de @nestjs/cache-manager para cache e express-session com connect-redis para persistência de sessões cobre os dois casos de uso mais comuns do Redis em aplicações backend.

Conceito Chave

O cache Redis no NestJS reduz a carga no banco de dados armazenando dados frequentemente acessados em memória. O gerenciamento de sessões com Redis permite o escalonamento horizontal ao compartilhar o estado das sessões entre múltiplas instâncias.

Configuração do @nestjs/cache-manager com Redis

O módulo cache-manager foi movido para um pacote separado no NestJS 10. Sua instalação requer tanto o wrapper do NestJS quanto o store de cache subjacente. O adaptador @keyv/redis é a abordagem recomendada para integração do Redis com cache-manager v5.

bash
# Instalação dos pacotes necessários
npm install @nestjs/cache-manager cache-manager @keyv/redis

O registro do módulo configura a conexão Redis e o TTL padrão. A opção isGlobal torna o cache disponível em todos os módulos sem necessidade de reimportação.

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 padrão em milissegundos
      }),
    }),
  ],
})
export class AppModule {}

O array stores aceita múltiplos stores de cache para caching em múltiplos níveis. Ambientes de produção tipicamente usam Redis como store principal, com um fallback opcional em memória para desenvolvimento.

Injeção do Cache e Caching em Nível de Serviço

O token CACHE_MANAGER fornece acesso direto ao cache para operações em nível de serviço. Essa abordagem oferece mais controle do que o caching HTTP automático quando a lógica de negócio determina a invalidação do cache.

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 cache primeiro
    const cacheKey = `user:${id}`;
    const cached = await this.cache.get<User>(cacheKey);
    
    if (cached) {
      return cached;
    }

    // Cache miss: buscar do banco de dados
    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 cache após atualização
    await this.cache.del(`user:${id}`);
    
    return user;
  }
}

O padrão cache-aside implementado acima verifica o cache antes de consultar o banco de dados. Operações de escrita invalidam a entrada do cache para garantir a consistência dos dados.

Decorator CacheInterceptor para Caching HTTP

O CacheInterceptor fornece caching automático de respostas para endpoints REST. Ele usa a URL da requisição como chave de cache por padrão, tornando-o ideal para endpoints GET que servem dados estáticos ou que mudam com pouca frequência.

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();
  }
}

O decorator @CacheTTL() sobrescreve o TTL padrão para rotas específicas. O decorator @CacheKey() permite chaves de cache personalizadas em vez do esquema padrão baseado em URL.

Gerenciamento de Sessões com Redis

O gerenciamento de sessões requer express-session com o store Redis. Essa configuração habilita o armazenamento persistente de sessões que sobrevive a reinicializações do servidor e funciona com múltiplas instâncias da aplicação.

bash
# Instalação das dependências de sessão
npm install express-session connect-redis ioredis
npm install -D @types/express-session

A configuração de sessão é feita no arquivo main.ts antes da inicialização da aplicação 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();

A opção resave: false evita operações de salvamento desnecessárias. A opção saveUninitialized: false previne a criação de sessões vazias, o que é importante para conformidade com a LGPD.

Acesso a Sessões em Controllers

O objeto de sessão fica disponível através do objeto request do Express. Um decorator personalizado simplifica o acesso a sessões nos handlers de rota.

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);
  }
}

Padrões Avançados de Caching

Aplicações de produção frequentemente requerem estratégias de caching mais sofisticadas. O padrão write-through atualiza o cache simultaneamente com o banco de dados, enquanto o padrão cache-aside carrega dados sob 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,
  ) {}

  // Padrão Write-Through
  async create(orderData: CreateOrderDto): Promise<Order> {
    const order = await this.ordersRepository.create(orderData);
    
    // Escrever no cache imediatamente
    await this.cache.set(`order:${order.id}`, order, 600000);
    
    // Invalidar caches de lista
    await this.invalidateListCaches(order.userId);
    
    return order;
  }

  // Padrão de prefixos de chaves de cache
  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');
  }
}

Gerenciamento de Conexão Redis e Health Checks

Aplicações de produção requerem monitoramento de conexões Redis e 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 }),
      );
    }
  }
}

Pronto para mandar bem nas entrevistas de Node.js / NestJS?

Pratique com nossos simuladores interativos, flashcards e testes tecnicos.

Perguntas Comuns de Entrevista sobre Caching com Redis

Entrevistas técnicas frequentemente avaliam a compreensão de padrões de caching e seus trade-offs. Essas perguntas cobrem conceitos fundamentais e cenários reais de produção.

P: Qual é a diferença entre os padrões cache-aside e write-through?

O padrão cache-aside carrega dados no cache apenas quando são acessados, o que reduz o uso de memória do cache mas pode criar cache misses iniciais. O padrão write-through atualiza o cache durante operações de escrita, garantindo que o cache sempre contenha dados atualizados mas aumentando a latência de escrita.

P: Como lidar com a invalidação de cache em um sistema distribuído?

As abordagens incluem: invalidação baseada em eventos usando Redis Pub/Sub, TTL com atualizações eventualmente consistentes, e estratégias de versionamento de chaves de cache. A escolha depende dos requisitos de consistência e da latência aceitável.

P: Quando usar caching em nível de sessão vs caching em nível de aplicação?

O cache em nível de sessão armazena dados específicos do usuário como preferências ou conteúdo do carrinho. O cache em nível de aplicação armazena dados compartilhados como catálogos de produtos ou configurações. Combinar ambos otimiza o uso de recursos.

P: Como implementar um cache distribuído com invalidação?

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> {
    // A implementação depende do cache store
    // Para Redis, usar SCAN com pattern matching
    const keys = await this.redis.keys(pattern);
    if (keys.length > 0) {
      await this.redis.del(...keys);
    }
  }
}

P: Como prevenir cache stampedes?

Cache stampedes ocorrem quando múltiplas requisições tentam simultaneamente regenerar uma entrada de cache expirada. As soluções incluem probabilistic early recomputation, travamento com semáforos, ou distribuição da expiração com TTLs aleatórios.

Boas Práticas de Produção

O deploy de aplicações NestJS baseadas em Redis em produção requer atenção especial ao gerenciamento de conexões e estratégias 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 {}

O parâmetro maxRetriesPerRequest evita que requisições fiquem bloqueadas indefinidamente durante falhas do Redis. A opção lazyConnect atrasa a conexão até a primeira operação, melhorando os tempos de inicialização.

Conclusão

A integração do Redis com NestJS oferece uma solução robusta para caching e gerenciamento de sessões. A combinação do cache-manager para cache de aplicação e connect-redis para sessões cobre a maioria dos casos de uso em produção. Dominar esses padrões é essencial para qualquer desenvolvedor NestJS que trabalhe em aplicações de alto tráfego.

Desafio do dia

Você saberia encontrar o bug em Node.js / NestJS?

Um trecho real, um bug escondido, uma tentativa por dia. Sem conta para testar.

Anthony Fillion-Maillet

Escrito por

Anthony Fillion-Maillet

Fundador da SharpSkill

Desenvolvedor fullstack há mais de 10 anos. Dirige a SharpSkill e responde por tudo o que é publicado aqui.

Atualizado em 10 de setembro de 2026

Tags

#nestjs
#redis
#caching
#sessões
#node.js

Compartilhar

Artigos relacionados