WebSocket em JavaScript: criando aplicações em tempo real com Node.js
A web moderna exige respostas instantâneas. Os usuários esperam que as mensagens no chat sejam entregues em milissegundos, as cotações de ações sejam atualizadas sem recarregar a página e as notificações cheguem em tempo real. A tecnologia WebSocket em JavaScript resolve esse problema, fornecendo uma conexão bidirecional permanente entre o cliente e o servidor. Neste artigo, vamos explorar como usar WebSocket no ambiente Node.js para criar aplicações em tempo real rápidas e escaláveis.
O que é WebSocket e por que ele é necessário?
O protocolo HTTP tradicional funciona no princípio de "requisição-resposta": o cliente envia uma requisição, o servidor responde e a conexão é encerrada. Para obter novos dados, o cliente precisa consultar o servidor constantemente (polling) ou usar requisições HTTP longas (long polling). Isso é ineficiente, cria carga excessiva e aumenta a latência.
WebSocket (RFC 6455) é um protocolo que estabelece uma conexão duplex permanente através de um único canal TCP. Após o handshake via HTTP, o servidor e o cliente podem enviar dados um para o outro a qualquer momento, sem cabeçalhos adicionais. Isso torna o WebSocket a solução ideal para:
- Chats e mensageiros
- Jogos online e edição colaborativa
- Cotações de bolsa e dashboards financeiros
- Notificações push e monitoramento
- Transmissão de dados em fluxo (IoT, logs)
Instalação e configuração do servidor WebSocket no Node.js
Para trabalhar com WebSocket no lado do servidor, usaremos a biblioteca ws — a implementação mais popular e confiável para Node.js. Vamos instalá-la via npm:
npm install wsVamos criar um servidor WebSocket simples. Primeiro, importamos o módulo e criamos uma instância do servidor na porta 8080:
const WebSocket = require('ws');
const wss = new WebSocket.Server({ port: 8080 });
wss.on('connection', function connection(ws) { console.log('Novo cliente conectado');
// Envia uma mensagem de boas-vindas ao cliente ws.send('Bem-vindo ao servidor WebSocket!');
// Processa mensagens recebidas ws.on('message', function incoming(message) { console.log('Recebido: %s', message); // Responde para todos os clientes conectados wss.clients.forEach(function each(client) { if (client.readyState === WebSocket.OPEN) { client.send(`Eco: ${message}`); } }); });
// Processa a desconexão do cliente ws.on('close', function close() { console.log('Cliente desconectado'); });
// Processa erros ws.on('error', function error(err) { console.error('Erro no WebSocket:', err); });});
console.log('Servidor WebSocket iniciado em ws://localhost:8080');Este código cria um servidor echo simples: qualquer mensagem enviada pelo cliente será retransmitida para todos os usuários conectados. Observe a verificação readyState — ela garante que não tentamos enviar dados para uma conexão fechada.
Parte cliente do WebSocket no navegador
No lado do cliente (navegador), o WebSocket é um objeto nativo — nenhuma biblioteca adicional é necessária. Vamos nos conectar ao nosso servidor e implementar um chat simples:
// Cria uma conexão WebSocketconst socket = new WebSocket('ws://localhost:8080');
// Manipulador de abertura da conexãosocket.addEventListener('open', function (event) { console.log('Conexão estabelecida'); socket.send('Olá, servidor!');});
// Manipulador de recebimento de mensagemsocket.addEventListener('message', function (event) { const messagesDiv = document.getElementById('messages'); const messageElement = document.createElement('div'); messageElement.textContent = event.data; messagesDiv.appendChild(messageElement);});
// Manipulador de errosocket.addEventListener('error', function (event) { console.error('Erro no WebSocket:', event);});
// Manipulador de fechamento da conexãosocket.addEventListener('close', function (event) { console.log('Conexão fechada');});Este código estabelece uma conexão com o servidor WebSocket, envia uma mensagem de saudação e exibe todas as mensagens recebidas na página. O WebSocket gerencia automaticamente a reconexão e o heartbeat, tornando a comunicação confiável mesmo em condições de rede instáveis.
Envio de dados estruturados: JSON sobre WebSocket
Na prática, raramente enviamos apenas strings simples. Normalmente, trocamos objetos de dados estruturados. O formato mais conveniente para isso é o JSON. Vamos modificar nosso servidor para processar mensagens JSON:
const WebSocket = require('ws');
const wss = new WebSocket.Server({ port: 8080 });
wss.on('connection', function connection(ws) { console.log('Novo cliente conectado');
ws.on('message', function incoming(data) { try { const message = JSON.parse(data); console.log('Mensagem recebida:', message);
// Processa diferentes tipos de mensagens switch(message.type) { case 'chat': // Broadcast da mensagem de chat para todos os clientes wss.clients.forEach(function each(client) { if (client.readyState === WebSocket.OPEN) { client.send(JSON.stringify({ type: 'chat', user: message.user, text: message.text, timestamp: Date.now() })); } }); break;
case 'join': // Notifica outros usuários sobre novo participante wss.clients.forEach(function each(client) { if (client !== ws && client.readyState === WebSocket.OPEN) { client.send(JSON.stringify({ type: 'system', text: `${message.user} entrou no chat` })); } }); break;
default: console.log('Tipo de mensagem desconhecido:', message.type); } } catch (e) { console.error('Erro ao processar JSON:', e); } });});
console.log('Servidor WebSocket JSON iniciado em ws://localhost:8080');No lado do cliente, enviamos dados estruturados usando JSON.stringify() e processamos as respostas com JSON.parse():
const socket = new WebSocket('ws://localhost:8080');
// Envia uma mensagem de chatfunction sendChatMessage(user, text) { const message = { type: 'chat', user: user, text: text }; socket.send(JSON.stringify(message));}
// Processa mensagens recebidassocket.addEventListener('message', function (event) { const data = JSON.parse(event.data);
switch(data.type) { case 'chat': displayChatMessage(data.user, data.text); break; case 'system': displaySystemMessage(data.text); break; default: console.log('Tipo de mensagem desconhecido:', data.type); }});
function displayChatMessage(user, text) { const messagesDiv = document.getElementById('messages'); const messageElement = document.createElement('div'); messageElement.innerHTML = `${user}: ${text}`; messagesDiv.appendChild(messageElement);}
function displaySystemMessage(text) { const messagesDiv = document.getElementById('messages'); const messageElement = document.createElement('div'); messageElement.className = 'system-message'; messageElement.textContent = text; messagesDiv.appendChild(messageElement);}O uso de JSON permite criar protocolos de aplicação flexíveis, onde cada mensagem contém informações sobre seu tipo e dados associados. Isso é especialmente útil para aplicações complexas com diferentes tipos de interações.
Gerenciamento de conexões e escalabilidade
Ao desenvolver aplicações WebSocket reais, é importante considerar o gerenciamento de conexões e a escalabilidade. Aqui estão algumas práticas recomendadas:
- Heartbeat: Envie mensagens ping/pong periodicamente para verificar se a conexão ainda está ativa e evitar desconexões por inatividade.
- Salas (Rooms): Agrupe conexões por tópicos ou canais para evitar broadcast desnecessário.
- Autenticação: Verifique tokens JWT ou cookies durante o handshake WebSocket.
- Limite de taxa: Implemente rate limiting para evitar sobrecarga do servidor.
- Balanceamento de carga: Use Redis ou outro mecanismo de pub/sub para sincronizar mensagens entre múltiplas instâncias do servidor.
Exemplo de implementação de heartbeat no servidor:
const WebSocket = require('ws');
const wss = new WebSocket.Server({ port: 8080 });
function heartbeat() { this.isAlive = true;}
wss.on('connection', function connection(ws) { ws.isAlive = true; ws.on('pong', heartbeat);});
// Envia ping para todos os clientes a cada 30 segundosconst interval = setInterval(function ping() { wss.clients.forEach(function each(ws) { if (ws.isAlive === false) { console.log('Cliente desconectado por inatividade'); return ws.terminate(); }
ws.isAlive = false; ws.ping(); });}, 30000);
wss.on('close', function close() { clearInterval(interval);});Conclusão
WebSocket é uma ferramenta poderosa para criar aplicações web interativas e em tempo real. Combinado com Node.js, ele oferece alto desempenho, facilidade de uso e excelente escalabilidade. Neste artigo, cobrimos os fundamentos: desde a criação de um servidor simples até o gerenciamento de conexões e troca de dados estruturados.
Para projetos reais, recomenda-se usar bibliotecas como Socket.IO, que fornece fallback para tecnologias mais antigas (long polling) e uma API mais conveniente. No entanto, entender o WebSocket puro é essencial para depuração e otimização de aplicações de alto desempenho.
Comece experimentando com exemplos simples, adicione gradualmente funcionalidades como autenticação e salas, e você terá uma base sólida para construir qualquer aplicação em tempo real.