WebSockets com ws e Socket.io
1. Introdução aos WebSockets no Ecossistema Node.js
WebSockets representam uma evolução significativa na comunicação web, permitindo conexões bidirecionais persistentes entre cliente e servidor. Diferentemente do HTTP/REST, onde o cliente sempre inicia a requisição e o servidor responde, os WebSockets estabelecem um canal aberto contínuo onde ambas as partes podem enviar dados a qualquer momento.
Casos de uso típicos incluem:
- Chats em tempo real: mensagens instantâneas sem polling
- Notificações ao vivo: alertas de sistemas, atualizações de status
- Jogos multiplayer: sincronização de estado entre jogadores
- Dashboards dinâmicos: atualização de gráficos e métricas
Duas bibliotecas dominam o ecossistema Node.js: ws (leve e minimalista) e Socket.io (rica em recursos como fallback, salas e reconexão automática).
2. Configuração do Ambiente e Primeiros Passos
Servidor com ws
Primeiro, instale o pacote:
npm install ws
Crie um servidor WebSocket mínimo:
const WebSocket = require('ws');
const wss = new WebSocket.Server({ port: 8080 });
wss.on('connection', (ws) => {
console.log('Cliente conectado');
ws.send('Bem-vindo ao servidor WebSocket!');
ws.on('message', (message) => {
console.log(`Recebido: ${message}`);
});
ws.on('close', () => {
console.log('Cliente desconectado');
});
});
console.log('Servidor WebSocket rodando em ws://localhost:8080');
Cliente no navegador
const socket = new WebSocket('ws://localhost:8080');
socket.onopen = () => {
console.log('Conectado ao servidor');
socket.send('Olá, servidor!');
};
socket.onmessage = (event) => {
console.log(`Mensagem do servidor: ${event.data}`);
};
socket.onclose = () => {
console.log('Conexão encerrada');
};
3. Comunicação Bidirecional com a Biblioteca ws
Envio e recebimento de mensagens JSON
const WebSocket = require('ws');
const wss = new WebSocket.Server({ port: 8080 });
wss.on('connection', (ws) => {
ws.on('message', (data) => {
try {
const parsed = JSON.parse(data);
console.log('Mensagem JSON recebida:', parsed);
// Resposta em JSON
ws.send(JSON.stringify({ status: 'ok', echo: parsed }));
} catch (e) {
ws.send(JSON.stringify({ error: 'Formato inválido' }));
}
});
});
Chat simples com broadcast
const WebSocket = require('ws');
const wss = new WebSocket.Server({ port: 8080 });
const clients = new Set();
wss.on('connection', (ws) => {
clients.add(ws);
ws.on('message', (message) => {
// Broadcast para todos os clientes
clients.forEach((client) => {
if (client !== ws && client.readyState === WebSocket.OPEN) {
client.send(message.toString());
}
});
});
ws.on('close', () => {
clients.delete(ws);
});
});
4. Introdução ao Socket.io no Backend (Node.js)
Instalação e configuração básica
npm install socket.io express
const express = require('express');
const http = require('http');
const { Server } = require('socket.io');
const app = express();
const server = http.createServer(app);
const io = new Server(server, {
cors: {
origin: 'http://localhost:3000',
methods: ['GET', 'POST']
}
});
io.on('connection', (socket) => {
console.log(`Usuário conectado: ${socket.id}`);
socket.on('chat message', (msg) => {
io.emit('chat message', { user: socket.id, message: msg });
});
socket.on('disconnect', () => {
console.log(`Usuário desconectado: ${socket.id}`);
});
});
server.listen(3001, () => {
console.log('Servidor Socket.io rodando na porta 3001');
});
5. Socket.io no Frontend (React)
Instalação do cliente
npm install socket.io-client
Hook customizado useSocket
import { useEffect, useState } from 'react';
import { io } from 'socket.io-client';
const useSocket = (url = 'http://localhost:3001') => {
const [socket, setSocket] = useState(null);
const [connected, setConnected] = useState(false);
useEffect(() => {
const newSocket = io(url);
newSocket.on('connect', () => setConnected(true));
newSocket.on('disconnect', () => setConnected(false));
setSocket(newSocket);
return () => newSocket.close();
}, [url]);
return { socket, connected };
};
export default useSocket;
Componente de chat em tempo real
import React, { useState, useEffect } from 'react';
import useSocket from './useSocket';
const ChatRoom = () => {
const { socket, connected } = useSocket();
const [messages, setMessages] = useState([]);
const [input, setInput] = useState('');
useEffect(() => {
if (!socket) return;
socket.on('chat message', (data) => {
setMessages((prev) => [...prev, data]);
});
return () => socket.off('chat message');
}, [socket]);
const sendMessage = (e) => {
e.preventDefault();
if (input.trim()) {
socket.emit('chat message', input);
setInput('');
}
};
return (
<div>
<h2>Chat em Tempo Real</h2>
<p>Status: {connected ? 'Conectado' : 'Desconectado'}</p>
<div style={{ border: '1px solid #ccc', height: 300, overflow: 'auto', padding: 10 }}>
{messages.map((msg, i) => (
<div key={i}>
<strong>{msg.user}:</strong> {msg.message}
</div>
))}
</div>
<form onSubmit={sendMessage}>
<input
value={input}
onChange={(e) => setInput(e.target.value)}
placeholder="Digite sua mensagem..."
/>
<button type="submit">Enviar</button>
</form>
</div>
);
};
export default ChatRoom;
6. Recursos Avançados do Socket.io
Salas (rooms) para comunicação em grupo
// Servidor
io.on('connection', (socket) => {
socket.join('sala-geral');
socket.on('join-room', (room) => {
socket.join(room);
io.to(room).emit('message', `${socket.id} entrou na sala ${room}`);
});
socket.on('room-message', ({ room, message }) => {
io.to(room).emit('message', { user: socket.id, message });
});
});
Broadcast e emissão para todos exceto o emissor
// Envia para todos, exceto o remetente
socket.broadcast.emit('user-typing', { user: socket.id });
// Envia para uma sala específica, exceto o remetente
socket.to('sala-geral').emit('notification', 'Novo usuário entrou');
Reconexão automática (configuração do cliente)
const socket = io('http://localhost:3001', {
reconnection: true,
reconnectionAttempts: 10,
reconnectionDelay: 1000,
reconnectionDelayMax: 5000,
timeout: 20000
});
7. Comparação e Boas Práticas
Quando usar ws vs Socket.io
| Critério | ws |
Socket.io |
|---|---|---|
| Performance | Excelente (baixo overhead) | Bom (ligeiramente maior) |
| Simplicidade | Mínimo, cru | Rico em recursos prontos |
| Fallback | Nenhum (apenas WebSocket) | Long-polling, FlashSocket |
| Reconexão | Manual | Automática |
| Salas/Namespaces | Manual | Nativo |
| Tamanho | ~5KB | ~50KB (cliente) |
Segurança
// Validação de origem no Socket.io
const io = new Server(server, {
cors: {
origin: 'https://meudominio.com',
credentials: true
}
});
// Rate limiting básico
const rateLimit = new Map();
io.use((socket, next) => {
const ip = socket.handshake.address;
const now = Date.now();
if (!rateLimit.has(ip)) {
rateLimit.set(ip, []);
}
const timestamps = rateLimit.get(ip).filter(t => now - t < 1000);
if (timestamps.length >= 5) {
return next(new Error('Muitas conexões'));
}
timestamps.push(now);
rateLimit.set(ip, timestamps);
next();
});
Escalabilidade com Redis adapter
npm install @socket.io/redis-adapter redis
const { createClient } = require('redis');
const { Server } = require('socket.io');
const { createAdapter } = require('@socket.io/redis-adapter');
const pubClient = createClient({ url: 'redis://localhost:6379' });
const subClient = pubClient.duplicate();
const io = new Server(server);
io.adapter(createAdapter(pubClient, subClient));
8. Integração com Temas Vizinhos da Série
WebSockets com autenticação JWT
const jwt = require('jsonwebtoken');
io.use((socket, next) => {
const token = socket.handshake.auth.token;
try {
const decoded = jwt.verify(token, process.env.JWT_SECRET);
socket.userId = decoded.id;
next();
} catch (err) {
next(new Error('Autenticação falhou'));
}
});
io.on('connection', (socket) => {
console.log(`Usuário ${socket.userId} conectado`);
});
Notificações em tempo real com Bull + Redis
const Queue = require('bull');
const notificationQueue = new Queue('notifications', 'redis://localhost:6379');
// Produtor
app.post('/api/notify', async (req, res) => {
await notificationQueue.add({
userId: req.body.userId,
message: req.body.message
});
res.json({ success: true });
});
// Consumidor
notificationQueue.process(async (job) => {
const { userId, message } = job.data;
io.to(`user-${userId}`).emit('notification', message);
});
Testes de WebSockets com Jest
const io = require('socket.io-client');
const { createServer } = require('http');
const { Server } = require('socket.io');
describe('WebSocket Tests', () => {
let server, clientSocket;
beforeAll((done) => {
const httpServer = createServer();
const ioServer = new Server(httpServer);
httpServer.listen(() => {
const port = httpServer.address().port;
clientSocket = io(`http://localhost:${port}`);
clientSocket.on('connect', done);
});
server = { httpServer, ioServer };
});
afterAll(() => {
clientSocket.close();
server.httpServer.close();
});
test('deve receber mensagem de boas-vindas', (done) => {
server.ioServer.on('connection', (socket) => {
socket.emit('welcome', 'Conectado!');
});
clientSocket.on('welcome', (msg) => {
expect(msg).toBe('Conectado!');
done();
});
});
});
Referências
- Documentação oficial do Socket.io — Guia completo de instalação, configuração e APIs do Socket.io para servidor e cliente
- Documentação do pacote ws — Repositório oficial com exemplos de uso, API de servidor e cliente WebSocket leve
- MDN Web Docs: WebSocket API — Referência completa da API WebSocket nativa do navegador
- Socket.io com React: tutorial prático — Guia oficial de integração do Socket.io com aplicações React
- WebSockets vs REST: quando usar cada um — Artigo técnico comparando WebSockets e REST com casos de uso práticos
- Redis adapter para Socket.io — Documentação oficial sobre escalabilidade com Redis para múltiplos servidores
- Autenticação JWT com Socket.io — Tutorial prático de integração de autenticação JWT com WebSockets