Ir para o conteúdo
Backend

API Gateway Patterns: Autenticação, Rate Limiting e Caching com Node.js

Marcos Soares
Atualizado em 
17 minutos de leitura
Ilustracao 3D de um portal gateway translucido com tres camadas luminosas representando autenticacao rate limiting e caching
Ouça este artigo
0:00API Gateway Patterns: Autenticação, Rate Limiting e Caching com Node.js--:--

Conteúdo técnico toda semana

Receba artigos sobre arquitetura, padrões de projeto e engenharia de software. Direto no seu e-mail, sem enrolação.

Sem spam. Cancele a qualquer momento com 1 clique.

Neste artigo

Um reverse proxy que só repassa requests é um nginx com extra steps. Um API Gateway de verdade resolve três problemas antes que o request chegue ao serviço de destino: quem é você (autenticação), com que frequência você pode bater aqui (rate limiting) e se eu preciso processar isso de novo (caching). O resto (transformação de payload, circuit breaking, observabilidade) é camada extra que só faz sentido depois que esses três estão sólidos.

Este post implementa cada um desses patterns com Express, Redis e TypeScript. Código completo, rodável, sem abstrações fictícias.

A anatomia de um gateway: ordem dos middlewares importa

A sequência em que você encadeia os middlewares define o comportamento do gateway. Inverter a ordem entre rate limiting e autenticação, por exemplo, muda completamente o perfil de segurança.

TypeScript
// src/gateway.ts
import express from 'express';
import { authMiddleware } from './middlewares/auth';
import { rateLimitMiddleware } from './middlewares/rateLimit';
import { cacheMiddleware } from './middlewares/cache';
import { proxyMiddleware } from './middlewares/proxy';
 
const app = express();
 
// 1. Rate limiting ANTES de autenticação: protege contra brute force no próprio endpoint de login
app.use(rateLimitMiddleware);
 
// 2. Autenticação: rejeita requests sem token válido antes de gastar I/O com cache ou proxy
app.use(authMiddleware);
 
// 3. Cache: evita bater no upstream se a resposta ainda é válida
app.use(cacheMiddleware);
 
// 4. Proxy: repassa para o serviço de destino só o que passou por tudo acima
app.use(proxyMiddleware);
 
app.listen(3000, () => {
  console.log('Gateway rodando na porta 3000');
});

Se você colocar autenticação antes de rate limiting, um atacante consegue disparar milhares de requests com tokens inválidos e forçar o gateway a verificar JWT (operação CPU-bound com verificação de assinatura) antes de qualquer throttling. O rate limiter na frente corta isso na raiz.

Autenticação centralizada com JWT e JWKS

Validar JWT no gateway significa que nenhum serviço downstream precisa conhecer chaves de assinatura. O gateway valida, extrai claims e repassa como headers confiáveis.

TypeScript
// src/middlewares/auth.ts
import { Request, Response, NextFunction } from 'express';
import jwt, { JwtHeader, SigningKeyCallback } from 'jsonwebtoken';
import jwksClient from 'jwks-rsa';
 
const client = jwksClient({
  jwksUri: process.env.JWKS_URI || 'https://auth.exemplo.com/.well-known/jwks.json',
  // Cache de chaves por 10 minutos: evita bater no JWKS endpoint a cada request
  cache: true,
  cacheMaxAge: 600_000,
  rateLimit: true,
  jwksRequestsPerMinute: 10,
});
 
function getKey(header: JwtHeader, callback: SigningKeyCallback): void {
  client.getSigningKey(header.kid, (err, key) => {
    if (err) return callback(err);
    const signingKey = key?.getPublicKey();
    callback(null, signingKey);
  });
}
 
const PUBLIC_PATHS = ['/health', '/api/v1/auth/login', '/api/v1/auth/register'];
 
export function authMiddleware(req: Request, res: Response, next: NextFunction): void {
  if (PUBLIC_PATHS.includes(req.path)) {
    return next();
  }
 
  const authHeader = req.headers.authorization;
  if (!authHeader?.startsWith('Bearer ')) {
    res.status(401).json({ error: 'Token ausente' });
    return;
  }
 
  const token = authHeader.slice(7);
 
  jwt.verify(token, getKey, { algorithms: ['RS256'] }, (err, decoded) => {
    if (err) {
      const status = err.name === 'TokenExpiredError' ? 401 : 403;
      res.status(status).json({ error: err.message });
      return;
    }
 
    // Headers X-User-* são confiáveis porque só o gateway os injeta
    // Serviços downstream NUNCA devem aceitar esses headers vindos do cliente
    const payload = decoded as { sub: string; role: string; tenant_id: string };
    req.headers['x-user-id'] = payload.sub;
    req.headers['x-user-role'] = payload.role;
    req.headers['x-tenant-id'] = payload.tenant_id;
 
    next();
  });
}

O uso de JWKS (JSON Web Key Set) em vez de uma chave simétrica compartilhada resolve dois problemas: rotação de chaves sem redeploy do gateway e eliminação de segredos compartilhados entre serviços. Se o seu provedor de identidade é Keycloak, Auth0 ou Cognito, todos expõem um endpoint JWKS. Para detalhes sobre outros vetores de segurança em APIs Node.js, o post sobre Segurança em APIs Node.js: OWASP Top 10 na Prática cobre os demais itens.

Rate limiting: sliding window com Redis

Existem quatro algoritmos clássicos de rate limiting. A escolha depende do perfil de tráfego:

AlgoritmoPrecisãoComplexidadeUso de memóriaMelhor para
Fixed windowBaixa (burst na borda)Mínima1 counter por chaveAPIs internas com tráfego previsível
Sliding window logAltaAlta1 timestamp por requestAPIs com SLA rígido
Sliding window counterBoa (aproximação)Média2 counters por chaveAPIs públicas (melhor custo-benefício)
Token bucketBoaMédia1 counter + timestampAPIs que permitem bursts controlados

O sliding window counter oferece o melhor equilíbrio. Ele interpola entre a janela atual e a anterior para suavizar o problema de burst na borda que o fixed window tem.

TypeScript
// src/middlewares/rateLimit.ts
import { Request, Response, NextFunction } from 'express';
import Redis from 'ioredis';
 
const redis = new Redis(process.env.REDIS_URL || 'redis://localhost:6379');
 
interface RateLimitConfig {
  windowMs: number;
  maxRequests: number;
}
 
// Configurações diferentes por tier: um usuário free não tem a mesma cota que um enterprise
const TIER_LIMITS: Record<string, RateLimitConfig> = {
  free:       { windowMs: 60_000, maxRequests: 30 },
  pro:        { windowMs: 60_000, maxRequests: 200 },
  enterprise: { windowMs: 60_000, maxRequests: 1000 },
  anonymous:  { windowMs: 60_000, maxRequests: 10 },
};
 
// Script Lua executado atomicamente no Redis: evita race conditions
// entre o GET e o INCR que aconteceriam com comandos separados
const SLIDING_WINDOW_SCRIPT = `
  local current_key = KEYS[1]
  local previous_key = KEYS[2]
  local max_requests = tonumber(ARGV[1])
  local window_ms = tonumber(ARGV[2])
  local now = tonumber(ARGV[3])
 
  local current_count = tonumber(redis.call('GET', current_key) or '0')
  local previous_count = tonumber(redis.call('GET', previous_key) or '0')
 
  local current_window_start = math.floor(now / window_ms) * window_ms
  local elapsed = now - current_window_start
  local weight = (window_ms - elapsed) / window_ms
 
  local estimated = math.floor(previous_count * weight) + current_count
 
  if estimated >= max_requests then
    return {0, estimated, max_requests}
  end
 
  redis.call('INCR', current_key)
  redis.call('PEXPIRE', current_key, window_ms * 2)
 
  return {1, estimated + 1, max_requests}
`;
 
export async function rateLimitMiddleware(
  req: Request,
  res: Response,
  next: NextFunction
): Promise<void> {
  const userId = req.headers['x-user-id'] as string | undefined;
  const tier = (req.headers['x-user-tier'] as string) || (userId ? 'free' : 'anonymous');
  const identifier = userId || req.ip || 'unknown';
 
  const config = TIER_LIMITS[tier] || TIER_LIMITS.anonymous;
  const now = Date.now();
  const currentWindow = Math.floor(now / config.windowMs);
 
  const currentKey = `rl:${identifier}:${currentWindow}`;
  const previousKey = `rl:${identifier}:${currentWindow - 1}`;
 
  try {
    const result = (await redis.eval(
      SLIDING_WINDOW_SCRIPT,
      2,
      currentKey,
      previousKey,
      config.maxRequests,
      config.windowMs,
      now
    )) as [number, number, number];
 
    const [allowed, current, limit] = result;
 
    res.setHeader('X-RateLimit-Limit', limit);
    res.setHeader('X-RateLimit-Remaining', Math.max(0, limit - current));
    res.setHeader('X-RateLimit-Reset', Math.ceil((currentWindow + 1) * config.windowMs / 1000));
 
    if (!allowed) {
      res.status(429).json({
        error: 'Rate limit excedido',
        retryAfter: Math.ceil(config.windowMs / 1000),
      });
      return;
    }
 
    next();
  } catch (err) {
    // Fail open: se o Redis cair, o request passa.
    // Fail closed (bloquear tudo) é mais seguro mas causa indisponibilidade total.
    // A decisão depende do seu SLA: se downtime é pior que abuso temporário, fail open.
    console.error('Rate limit check failed:', err);
    next();
  }
}

O script Lua é executado atomicamente pelo Redis. Sem ele, você teria uma race condition entre ler o contador e incrementá-lo, permitindo que requests concorrentes ultrapassem o limite. Essa é a diferença entre um rate limiter que funciona em teste local e um que funciona com 500 requests simultâneos.

Caching: stale-while-revalidate no gateway

Cache no gateway é diferente de cache no CDN. O gateway conhece o contexto do usuário (tenant, role, plano) e pode cachear respostas por combinações que um CDN genérico não consegue.

TypeScript
// src/middlewares/cache.ts
import { Request, Response, NextFunction } from 'express';
import Redis from 'ioredis';
import crypto from 'crypto';
 
const redis = new Redis(process.env.REDIS_URL || 'redis://localhost:6379');
 
interface CachedResponse {
  status: number;
  headers: Record<string, string>;
  body: string;
  cachedAt: number;
}
 
// Só cacheia GET: mutações (POST, PUT, DELETE, PATCH) nunca devem ser cacheadas
const CACHEABLE_METHODS = new Set(['GET', 'HEAD']);
 
// TTL por prefixo de rota: catálogos mudam pouco, dashboards mudam muito
const ROUTE_TTL: Record<string, { ttl: number; stale: number }> = {
  '/api/v1/products':   { ttl: 300, stale: 600 },   // 5min fresh, 10min stale
  '/api/v1/categories': { ttl: 3600, stale: 7200 },  // 1h fresh, 2h stale
  '/api/v1/dashboard':  { ttl: 30, stale: 60 },      // 30s fresh, 1min stale
};
 
function buildCacheKey(req: Request): string {
  const tenantId = req.headers['x-tenant-id'] || 'public';
  const role = req.headers['x-user-role'] || 'anonymous';
 
  // Inclui tenant e role na chave: mesma rota retorna dados diferentes por tenant
  const raw = `${tenantId}:${role}:${req.method}:${req.originalUrl}`;
  return `cache:${crypto.createHash('sha256').update(raw).digest('hex')}`;
}
 
function findRouteTTL(path: string): { ttl: number; stale: number } {
  for (const [prefix, config] of Object.entries(ROUTE_TTL)) {
    if (path.startsWith(prefix)) return config;
  }
  return { ttl: 60, stale: 120 };
}
 
export async function cacheMiddleware(
  req: Request,
  res: Response,
  next: NextFunction
): Promise<void> {
  if (!CACHEABLE_METHODS.has(req.method)) {
    return next();
  }
 
  const cacheKey = buildCacheKey(req);
  const { ttl, stale } = findRouteTTL(req.path);
 
  try {
    const cached = await redis.get(cacheKey);
 
    if (cached) {
      const entry: CachedResponse = JSON.parse(cached);
      const age = Math.floor((Date.now() - entry.cachedAt) / 1000);
 
      if (age < ttl) {
        // Fresh: retorna direto
        res.setHeader('X-Cache', 'HIT');
        res.setHeader('Age', age);
        res.status(entry.status);
        for (const [key, value] of Object.entries(entry.headers)) {
          res.setHeader(key, value);
        }
        res.send(entry.body);
        return;
      }
 
      if (age < stale) {
        // Stale: retorna o cache mas dispara revalidação em background
        res.setHeader('X-Cache', 'STALE');
        res.setHeader('Age', age);
        res.status(entry.status);
        for (const [key, value] of Object.entries(entry.headers)) {
          res.setHeader(key, value);
        }
        res.send(entry.body);
 
        // Revalidação assíncrona: não bloqueia a resposta ao cliente
        revalidateInBackground(req, cacheKey, stale).catch(console.error);
        return;
      }
    }
  } catch (err) {
    console.error('Cache read failed:', err);
  }
 
  // Cache miss ou expirado: intercepta a resposta para cachear depois
  const originalJson = res.json.bind(res);
  res.json = function (body: unknown) {
    const entry: CachedResponse = {
      status: res.statusCode,
      headers: { 'content-type': 'application/json' },
      body: JSON.stringify(body),
      cachedAt: Date.now(),
    };
 
    // Só cacheia respostas 2xx: erros não devem ser cacheados
    if (res.statusCode >= 200 && res.statusCode < 300) {
      redis.setex(cacheKey, stale, JSON.stringify(entry)).catch(console.error);
    }
 
    res.setHeader('X-Cache', 'MISS');
    return originalJson(body);
  };
 
  next();
}
 
async function revalidateInBackground(
  req: Request,
  cacheKey: string,
  staleTtl: number
): Promise<void> {
  // Implementação simplificada: em produção, use uma fila (BullMQ, por exemplo)
  // para evitar thundering herd quando múltiplos requests stale chegam ao mesmo tempo
  const lockKey = `lock:${cacheKey}`;
  const acquired = await redis.set(lockKey, '1', 'EX', 10, 'NX');
  if (!acquired) return; // Outro processo já está revalidando
 
  // Aqui você faria o fetch ao upstream e atualizaria o cache
  // Omitido por brevidade: o padrão é o mesmo do proxy middleware
}

A invalidação por prefixo de rota com TTLs diferentes é o mínimo viável. Se você precisa de invalidação ativa (o serviço avisa o gateway que o dado mudou), o padrão é pub/sub no Redis ou um webhook do serviço para o gateway. Para aplicações que usam CQRS e Event Sourcing, a invalidação de cache pode ser disparada pelo próprio event handler.

O que NÃO fazer

Anti-pattern 1: rate limiting por IP sem fallback

TypeScript
// ERRADO: em ambientes com NAT ou proxies corporativos,
// milhares de usuários compartilham o mesmo IP
function getBadIdentifier(req: Request): string {
  return req.ip || 'unknown';
}

Atrás de um load balancer ou proxy corporativo, req.ip retorna o IP do proxy, não do cliente. Resultado: todos os usuários daquela rede compartilham a mesma cota.

TypeScript
// CORRETO: combina user ID autenticado com IP como fallback
function getIdentifier(req: Request): string {
  const userId = req.headers['x-user-id'] as string | undefined;
  if (userId) return `user:${userId}`;
 
  // Para requests não autenticados, usa IP + fingerprint do User-Agent
  const forwarded = req.headers['x-forwarded-for'];
  const ip = typeof forwarded === 'string' ? forwarded.split(',')[0].trim() : req.ip;
  const ua = req.headers['user-agent'] || '';
  const hash = crypto.createHash('md5').update(`${ip}:${ua}`).digest('hex').slice(0, 12);
  return `anon:${hash}`;
}

Anti-pattern 2: cachear respostas com dados específicos do usuário na mesma chave

TypeScript
// ERRADO: mesma chave para todos os usuários
// O usuário A recebe os dados do usuário B
function buildBadCacheKey(req: Request): string {
  return `cache:${req.method}:${req.originalUrl}`;
}
TypeScript
// CORRETO: inclui tenant e role na chave
function buildCacheKey(req: Request): string {
  const tenantId = req.headers['x-tenant-id'] || 'public';
  const role = req.headers['x-user-role'] || 'anonymous';
  const raw = `${tenantId}:${role}:${req.method}:${req.originalUrl}`;
  return `cache:${crypto.createHash('sha256').update(raw).digest('hex')}`;
}

Esse bug é silencioso: funciona perfeitamente em testes com um único usuário. Aparece só quando dois tenants acessam a mesma rota e o segundo recebe os dados do primeiro.

Anti-pattern 3: validar JWT sem verificar algoritmo

TypeScript
// ERRADO: aceita qualquer algoritmo, incluindo "none"
jwt.verify(token, secret);
TypeScript
// CORRETO: força RS256 e rejeita tokens com algoritmo diferente
jwt.verify(token, getKey, { algorithms: ['RS256'] });

O ataque de "algorithm confusion" funciona assim: o atacante envia um token assinado com HS256 usando a chave pública RSA como segredo simétrico. Sem a restrição de algoritmo, a biblioteca aceita. A documentação do jsonwebtoken no npm recomenda explicitamente setar algorithms.

O proxy: repassando para o upstream

TypeScript
// src/middlewares/proxy.ts
import { Request, Response, NextFunction } from 'express';
import { createProxyMiddleware, Options } from 'http-proxy-middleware';
 
// Mapa de rotas para serviços upstream
const ROUTE_MAP: Record<string, string> = {
  '/api/v1/products':   'http://product-service:4001',
  '/api/v1/orders':     'http://order-service:4002',
  '/api/v1/users':      'http://user-service:4003',
};
 
function resolveTarget(path: string): string | null {
  for (const [prefix, target] of Object.entries(ROUTE_MAP)) {
    if (path.startsWith(prefix)) return target;
  }
  return null;
}
 
export function proxyMiddleware(req: Request, res: Response, next: NextFunction): void {
  const target = resolveTarget(req.path);
 
  if (!target) {
    res.status(404).json({ error: 'Rota não encontrada no gateway' });
    return;
  }
 
  // Remove headers que o cliente não deveria controlar
  delete req.headers['x-forwarded-host'];
 
  const proxy = createProxyMiddleware({
    target,
    changeOrigin: true,
    timeout: 10_000,
    proxyTimeout: 10_000,
    // Não repassa cookies do gateway para o upstream: cada serviço gerencia sua sessão
    cookieDomainRewrite: '',
    on: {
      error: (err, _req, res) => {
        console.error(`Proxy error para ${target}:`, err.message);
        (res as Response).status(502).json({ error: 'Serviço indisponível' });
      },
    },
  });
 
  proxy(req, res, next);
}

Para ambientes containerizados, os nomes de host (product-service, order-service) vêm do DNS interno do Docker ou Kubernetes. O post sobre Docker para Devs: do Dockerfile ao docker-compose em Produção mostra como configurar a rede entre containers. Se você está deployando na edge, o Cloudflare Workers é uma alternativa ao gateway tradicional para cenários com latência como prioridade.

Quando usar gateway próprio vs. managed

CritérioGateway próprio (Express/Fastify)Managed (AWS API Gateway, Kong, Traefik)
Controle sobre lógicaTotalLimitado a plugins/extensões
Custo até 1M req/mêsCusto do servidor (~$20-50)Free tier ou ~$3.50/milhão
Custo acima de 100M req/mêsCusto fixo do servidor$350+/mês só de gateway
Latência adicionada~1-5ms (mesmo host/rede)~10-30ms (varia por provider)
Manutenção operacionalAlta (você mantém)Baixa (provider mantém)
ObservabilidadeVocê instrumentaBuilt-in (CloudWatch, dashboards)
Multi-cloudSimVendor lock-in

Se a sua API tem menos de 50M requests/mês e você não precisa de lógica customizada no gateway (transformação de payload, orquestração de múltiplos serviços), um managed gateway é a escolha pragmática. Acima disso, ou quando você precisa de lógica de negócio no gateway (multi-tenancy com isolamento de dados, por exemplo), o custo e a rigidez do managed não compensam.

Para quem já usa feature flags próprias, a mentalidade é a mesma: controle total tem custo operacional, mas elimina dependência de vendor e limites arbitrários de pricing.

Observabilidade mínima: o que medir

Sem métricas, o gateway é uma caixa preta. Três métricas são obrigatórias:

TypeScript
// src/middlewares/metrics.ts
import { Request, Response, NextFunction } from 'express';
 
// Contadores em memória para exemplo. Em produção, use prom-client com Prometheus.
const metrics = {
  requestsTotal: 0,
  cacheHits: 0,
  cacheMisses: 0,
  rateLimitBlocked: 0,
  upstreamErrors: 0,
  latencyBuckets: new Map<string, number[]>(),
};
 
export function metricsMiddleware(req: Request, res: Response, next: NextFunction): void {
  const start = process.hrtime.bigint();
 
  res.on('finish', () => {
    const durationMs = Number(process.hrtime.bigint() - start) / 1_000_000;
    metrics.requestsTotal++;
 
    const cacheHeader = res.getHeader('x-cache') as string | undefined;
    if (cacheHeader === 'HIT' || cacheHeader === 'STALE') metrics.cacheHits++;
    if (cacheHeader === 'MISS') metrics.cacheMisses++;
    if (res.statusCode === 429) metrics.rateLimitBlocked++;
    if (res.statusCode >= 500) metrics.upstreamErrors++;
 
    // Agrupa latência por rota para identificar endpoints lentos
    const route = req.route?.path || req.path;
    if (!metrics.latencyBuckets.has(route)) {
      metrics.latencyBuckets.set(route, []);
    }
    metrics.latencyBuckets.get(route)!.push(durationMs);
  });
 
  next();
}

Cache hit ratio abaixo de 60% em rotas de leitura indica que os TTLs estão curtos demais ou que as chaves de cache estão granulares demais. Rate limit blocked acima de 5% do tráfego total sugere que os limites estão agressivos ou que há abuso real que precisa de investigação.

Posição técnica: comece sem gateway, adicione quando doer

A tentação de criar um API Gateway desde o dia zero é forte. Resista. Com menos de 3 serviços, o gateway é overhead puro: mais um serviço para deployar, monitorar e manter. Coloque autenticação e rate limiting diretamente nos serviços usando os mesmos middlewares mostrados aqui.

O gateway se justifica quando: você tem 4+ serviços e está duplicando lógica de autenticação em cada um; precisa de rate limiting unificado (o usuário tem uma cota global, não por serviço); ou precisa de caching centralizado com invalidação coordenada.

Quando chegar nesse ponto, extraia os middlewares que já existem nos serviços para o gateway. A migração é incremental: rota por rota, serviço por serviço. Se a sua API já segue os patterns de segurança do OWASP e tem migrations seguras em produção, o gateway é a próxima camada natural de maturidade operacional.

FAQ

Preciso de Redis para rate limiting ou posso usar memória local?

Memória local funciona se você tem uma única instância do gateway. Com duas ou mais instâncias atrás de um load balancer, cada instância mantém seu próprio contador e o usuário consegue o dobro (ou triplo) da cota real. Redis centraliza o estado. Se Redis é demasia para o seu cenário, use o header X-Forwarded-For com sticky sessions no load balancer, mas isso é um workaround, não uma solução.

JWT ou sessão no gateway?

JWT para APIs stateless com múltiplos serviços downstream. Sessão (cookie + store) para aplicações web monolíticas ou quando você precisa de revogação instantânea (logout imediato). JWT não tem revogação nativa: o token é válido até expirar. Se precisa de revogação com JWT, mantenha uma blocklist no Redis e verifique no gateway, mas isso adiciona uma chamada ao Redis por request, anulando parte da vantagem de ser stateless.

Como invalido o cache quando o dado muda?

Três estratégias, em ordem de complexidade: (1) TTL curto e aceitar stale data por alguns segundos. (2) Invalidação explícita via DELETE no Redis quando o serviço upstream processa uma mutação. (3) Pub/sub no Redis: o serviço publica um evento de invalidação e o gateway assina. Para a maioria das APIs, a estratégia 1 com stale-while-revalidate resolve. A estratégia 3 é necessária quando stale data causa problemas de negócio (estoque, saldo financeiro).

O gateway não vira single point of failure?

Sim, e por isso precisa de pelo menos duas instâncias com health check. O gateway é stateless (todo estado está no Redis), então escalar horizontalmente é trivial: mais instâncias atrás do load balancer. O Redis é o ponto de falha real. Use Redis Sentinel ou Redis Cluster para alta disponibilidade. Se o Redis cair e o gateway faz fail open no rate limiting (como no código acima), o sistema continua funcionando sem proteção temporária, o que é aceitável para a maioria dos cenários.

Posso usar o mesmo gateway para WebSocket?

Pode, mas o caching e o rate limiting precisam de adaptação. WebSocket é uma conexão persistente: o rate limiting deve contar mensagens por conexão, não connections por segundo. O caching não se aplica a WebSocket da mesma forma que a HTTP. Se você tem WebSocket com Node.js, mantenha o upgrade de protocolo no gateway mas route as conexões diretamente para o serviço de WebSocket sem passar pelos middlewares de cache.

Marcos Soares

Escrito por

Marcos Soares

Fullstack Developer · CEO da Agência Poti

Fullstack Developer e CEO da Agência Poti. Mais de 20 anos construindo arquiteturas cloud-native com React, Next.js e sistemas distribuídos. Parceiro comercial do estúdio iellou design. Fundador do Vivo de Código.

Comentários

Participe da discussão

Seja o primeiro a comentar!

Continue Aprofundando

Conteúdo técnico toda semana

Receba artigos sobre arquitetura, padrões de projeto e engenharia de software. Direto no seu e-mail, sem enrolação.

Sem spam. Cancele a qualquer momento com 1 clique.