Home > Blog > Desenvolvimento Web
Desenvolvimento Web
JavaScript
Programação

WebSocket Nativo no Node.js

Atualizado em: 18 de agosto de 2026

Rack de servidores processando fluxos de dados no Node.js

O WebSocket Nativo no Node.js permite criar clientes de comunicação bidirecional usando a classe global WebSocket, sem instalar um pacote para o caso básico. A interface é compatível com a API dos navegadores e usa eventos como open, message, error e close.

A classe foi adicionada no Node.js 20.10.0 e 21.0.0, deixou de exigir a flag experimental no Node.js 22.0.0 e tornou-se estável no Node.js 22.4.0. No Node.js 26.5.0, continua disponível globalmente, embora possa ser desativada pela opção --no-experimental-websocket.

Neste guia, você aprenderá a conectar, enviar texto e bytes, receber mensagens, usar subprotocolos, autenticar, implementar heartbeat, reconectar com backoff, limitar filas, fechar corretamente, testar e entender por que a API nativa é um cliente, não um servidor WebSocket.

O que é o WebSocket nativo?

É uma implementação compatível com a Web da classe WebSocket. A documentação oficial do WebSocket global no Node.js apresenta o histórico e a disponibilidade. A documentação da API WebSocket na MDN explica propriedades, eventos e métodos.

Para conceitos de protocolo e servidores, consulte WebSocket com Node.js. Para o modelo de eventos usado pela classe, veja EventTarget no Node.js. O artigo de HTTPS e TLS no Node.js ajuda a proteger conexões wss:.

Cliente, não servidor

A classe global abre uma conexão para um servidor existente:

const socket = new WebSocket(
  'wss://example.com/realtime'
);

Ela não cria um servidor que aceita upgrades HTTP. Para servir WebSocket, use uma biblioteca ou infraestrutura com suporte ao lado servidor.

Evento open

socket.addEventListener('open', () => {
  console.log('Conectado');
});

Somente depois de open a conexão está pronta para envio normal.

Enviando texto

socket.addEventListener('open', () => {
  socket.send(JSON.stringify({
    type: 'subscribe',
    channel: 'orders'
  }));
});

Defina um protocolo de aplicação com campo de versão, tipo e payload validado.

Recebendo mensagens

socket.addEventListener('message', event => {
  console.log('Recebido:', event.data);
});

event.data pode ser string ou dados binários, conforme a mensagem e a configuração.

Validando JSON

socket.addEventListener('message', event => {
  if (typeof event.data !== 'string') return;

  let message;
  try {
    message = JSON.parse(event.data);
  } catch {
    return;
  }

  if (!isValidMessage(message)) return;
  handleMessage(message);
});

Nunca confie em mensagens recebidas, mesmo quando o servidor pertence à mesma empresa.

Mensagens binárias

socket.binaryType = 'arraybuffer';

socket.addEventListener('message', event => {
  if (event.data instanceof ArrayBuffer) {
    const bytes = new Uint8Array(event.data);
    processBytes(bytes);
  }
});

Defina limites antes de analisar imagens, arquivos ou protocolos binários.

Enviando bytes

const bytes = new Uint8Array([1, 2, 3]);
socket.send(bytes);

Buffer e TypedArrays precisam ser testados conforme o contrato esperado pelo servidor. Consulte Buffer no Node.js.

Estados da conexão

A propriedade readyState pode ter estados equivalentes a:

  • CONNECTING;
  • OPEN;
  • CLOSING;
  • CLOSED.
if (socket.readyState === WebSocket.OPEN) {
  socket.send(message);
}

Evento close

socket.addEventListener('close', event => {
  console.log({
    code: event.code,
    reason: event.reason,
    clean: event.wasClean
  });
});

O código ajuda a decidir se a reconexão é apropriada. Não repita indefinidamente quando o servidor rejeita autenticação ou protocolo.

Fechando corretamente

socket.close(1000, 'Encerramento normal');

O código 1000 representa fechamento normal. A razão possui limite de tamanho e deve ser texto válido.

Evento error

socket.addEventListener('error', event => {
  logger.error('websocket_error');
});

O evento pode não expor todos os detalhes da falha. Use logs do servidor, métricas e o evento close para diagnóstico.

Subprotocolos

const socket = new WebSocket(
  'wss://example.com/realtime',
  ['json.v2', 'json.v1']
);

O servidor seleciona um protocolo suportado. Após open:

console.log(socket.protocol);

Valide que o protocolo escolhido é conhecido antes de processar mensagens.

Autenticação

A API de navegador não permite configurar headers arbitrários no construtor da mesma forma que um cliente HTTP tradicional. Estratégias comuns incluem:

  • cookie já estabelecido;
  • token de curta duração na URL;
  • subprotocolo controlado;
  • mensagem de autenticação logo após open.

Evite tokens permanentes em query strings, porque URLs aparecem em logs.

Token temporário

Uma API HTTPS pode emitir um ticket de uso único e curta validade. O cliente usa esse ticket para abrir o WebSocket. Consulte Fetch Nativo no Node.js.

wss em produção

Use wss: para proteger tráfego e credenciais. Valide certificados e hostname. Não desative segurança TLS para contornar ambientes mal configurados.

Origem

Servidores WebSocket podem validar o header Origin. Um cliente Node.js não está sujeito ao mesmo modelo de segurança do navegador, portanto o servidor não deve depender apenas de Origin para autenticação.

Heartbeat

Proxies e balanceadores podem encerrar conexões ociosas. A API WebSocket de alto nível não expõe necessariamente frames ping e pong como bibliotecas específicas. Implemente heartbeat no protocolo de aplicação quando necessário:

socket.send(JSON.stringify({
  type: 'ping',
  time: Date.now()
}));

Resposta pong

if (message.type === 'pong') {
  lastPongAt = Date.now();
}

Feche e reconecte quando o prazo for excedido. Não deixe timers sem cleanup.

Timer de heartbeat

const timer = setInterval(() => {
  if (socket.readyState === WebSocket.OPEN) {
    sendHeartbeat();
  }
}, 30000);

timer.unref();

Limpe no close. Veja Timers Promises no Node.js para loops canceláveis.

Reconexão

WebSocket não reconecta automaticamente. Implemente uma máquina de estado.

async function reconnect(attempt, signal) {
  const maximum = 30000;
  const base = Math.min(
    500 * 2 ** attempt,
    maximum
  );
  const wait = Math.random() * base;

  await delay(wait, undefined, { signal });
  return connect();
}

Backoff com jitter

Jitter evita que milhares de clientes reconectem no mesmo instante após uma indisponibilidade.

Consulte Retry com Backoff no Node.js.

Quando não reconectar?

  • fechamento solicitado pelo usuário;
  • credencial inválida;
  • subprotocolo incompatível;
  • limite de tentativas excedido;
  • shutdown da aplicação;
  • erro de configuração permanente.

Fila durante desconexão

Não acumule mensagens sem limite enquanto o socket está fechado. Defina:

  • quantidade máxima;
  • tamanho máximo;
  • tempo de validade;
  • tipos que podem ser descartados;
  • operações que exigem confirmação.

bufferedAmount

if (socket.bufferedAmount > MAX_BUFFERED_BYTES) {
  pauseProducer();
}

A propriedade indica bytes enfileirados para envio. Ela não fornece uma Promise de drain, então o produtor precisa aplicar sua própria política.

Backpressure

A API WebSocket padrão não possui backpressure tão expressivo quanto streams. Enviar mais rápido que a rede pode consumir memória.

Mensagens grandes

Evite mensagens gigantes. Divida dados ou use HTTP para arquivos. WebSocket é mais adequado para eventos e mensagens de tamanho controlado.

Confirmação de entrega

send() não significa que o servidor processou a mensagem. Para operações importantes, inclua ID e resposta de confirmação:

{
  "id": "op-123",
  "type": "create-order",
  "payload": {}
}

Timeout de confirmação

Mantenha um Map limitado de operações pendentes e expire cada entrada. Remova tudo no close.

Idempotência

Após reconexão, uma mensagem pode ser reenviada. O servidor precisa reconhecer IDs já processados para evitar efeitos duplicados.

Ordem

WebSocket preserva ordem dos frames na conexão, mas reconexões e reenvios podem alterar a ordem lógica. Inclua sequência ou versão quando necessário.

Versionamento do protocolo

{
  "version": 2,
  "type": "order-updated",
  "sequence": 18,
  "payload": {}
}

Rejeite mensagens de versão desconhecida de forma controlada.

Validação

Use schema para verificar:

  • tipo;
  • versão;
  • campos obrigatórios;
  • tamanho de strings;
  • faixas numéricas;
  • profundidade;
  • quantidade de itens.

Compressão

Negociação de compressão depende do cliente e servidor. Mensagens comprimidas podem consumir CPU e expandir muito. Defina limites no lado servidor.

Proxy

Ambientes corporativos podem exigir proxy, recurso que a interface padrão pode não configurar diretamente. Uma biblioteca baseada em Undici ou WebSocket especializada pode ser necessária.

Certificados de cliente

mTLS e opções avançadas de conexão podem exigir um cliente com dispatcher ou configuração mais detalhada que a classe global.

Quando usar biblioteca?

Considere uma biblioteca quando precisa de:

  • servidor WebSocket;
  • headers customizados;
  • proxy avançado;
  • ping e pong de protocolo;
  • controle de compressão;
  • streams e backpressure específicos;
  • compatibilidade com versões antigas do Node.js.

Quando usar a API nativa?

  • cliente simples;
  • Node.js moderno;
  • interface compatível com navegador;
  • texto e binário básicos;
  • subprotocolos;
  • menos dependências.

EventTarget

A classe usa addEventListener(). Guarde referências ou use AbortSignal para remover listeners.

AbortController para cleanup

const listeners = new AbortController();

socket.addEventListener(
  'message',
  handleMessage,
  { signal: listeners.signal }
);

listeners.abort();

Fechar o socket e remover listeners são operações distintas.

Shutdown

async function shutdown() {
  reconnectController.abort();
  clearInterval(heartbeatTimer);

  if (socket.readyState === WebSocket.OPEN) {
    socket.close(1000, 'Application shutdown');
  }
}

Imponha prazo máximo para não bloquear o encerramento. Consulte Graceful Shutdown no Node.js.

Observabilidade

Registre:

  • tentativas de conexão;
  • tempo até open;
  • close code;
  • reconexões;
  • mensagens recebidas e enviadas;
  • bytes;
  • bufferedAmount;
  • timeouts de confirmação;
  • erros de validação.

Não registre tokens nem payloads sensíveis.

Métricas

Acompanhe conexões ativas, duração, taxa de reconexão, latência do heartbeat e tamanho da fila.

Testando com servidor local

Suba um servidor controlado em porta aleatória e teste:

  • open;
  • mensagem de texto;
  • binário;
  • subprotocolo;
  • close normal;
  • queda abrupta;
  • reconexão;
  • fila limitada;
  • heartbeat;
  • mensagem inválida.

Esperando open em teste

await new Promise((resolve, reject) => {
  socket.addEventListener('open', resolve, {
    once: true
  });
  socket.addEventListener('error', reject, {
    once: true
  });
});

Adicione timeout para evitar teste pendurado.

Testando reconexão

Injete a função de delay e o construtor do socket. Assim, o teste controla tempo e conexões sem esperar segundos reais.

Testando backpressure

Simule bufferedAmount alto e confirme que o produtor pausa ou descarta mensagens de baixa prioridade.

Segurança

  • use wss;
  • valide mensagens;
  • limite tamanho;
  • autentique cedo;
  • expire tokens;
  • aplique rate limit;
  • não confie apenas em Origin;
  • trate duplicidade;
  • restrinja subprotocolos;
  • não registre credenciais.

Erros comuns

  • Usar como servidor: a classe global é cliente.
  • Enviar antes de open: readyState não permite.
  • Fila ilimitada: memória cresce durante desconexão.
  • Reconectar sem jitter: ocorre tempestade de conexões.
  • Confiar em send: processamento remoto não foi confirmado.
  • Sem heartbeat: conexão morta parece ativa.
  • Usar ws em produção: tráfego fica sem TLS.

Boas práticas

  • Use Node.js 22.4 ou superior.
  • Trate a API como cliente.
  • Aguarde open.
  • Valide toda mensagem.
  • Limite filas e tamanhos.
  • Monitore bufferedAmount.
  • Use heartbeat.
  • Reconecte com backoff e jitter.
  • Feche no shutdown.
  • Use biblioteca quando precisar de recursos avançados.

Conclusão

O WebSocket Nativo no Node.js oferece um cliente estável e compatível com navegadores desde o Node.js 22.4.0. Ele atende conexões básicas com texto, binário, subprotocolos e eventos sem adicionar dependência.

A simplicidade não elimina responsabilidades de produção. A aplicação precisa autenticar, validar mensagens, limitar filas, implementar heartbeat, reconectar com jitter e fechar corretamente. Para servidor, proxy avançado, ping de protocolo ou controle detalhado, uma biblioteca especializada continua sendo a escolha adequada.

Os 10 Melhores Cursos de Programação de 2026

Descubra os melhores cursos de programação. Aprenda a escolher o curso ideal para iniciar ou avançar na carreira de desenvolvedor

POSTS RELACIONADOS

Ver todos

Seta para a direita