WebSocket com Node.js na VPS
WebSocket permite comunicação bidirecional em tempo real entre servidor e cliente — essencial para chats, dashboards ao vivo, notificações push e jogos multiplayer. Na VPS, o Nginx precisa de configuração específica para fazer proxy de conexões WebSocket, e aplicações com múltiplos workers exigem um adapter Redis para sincronizar eventos entre processos.
Socket.IO: servidor WebSocket com fallback
Socket.IO é a biblioteca mais popular — inclui fallback para long-polling quando WebSocket não está disponível e suporte nativo a salas:
// src/index.ts
import express from 'express'
import { createServer } from 'http'
import { Server } from 'socket.io'
const app = express()
const httpServer = createServer(app)
const io = new Server(httpServer, {
cors: {
origin: process.env.ALLOWED_ORIGINS?.split(',') || [],
methods: ['GET', 'POST'],
},
// Configurações para produção
pingTimeout: 60000,
pingInterval: 25000,
transports: ['websocket', 'polling'],
})
io.on('connection', (socket) => {
console.log('Cliente conectado:', socket.id)
socket.on('join-room', (roomId: string) => {
socket.join(roomId)
socket.to(roomId).emit('user-joined', { userId: socket.id })
})
socket.on('message', (data: { room: string; text: string }) => {
io.to(data.room).emit('message', {
from: socket.id,
text: data.text,
timestamp: new Date().toISOString(),
})
})
socket.on('disconnect', (reason) => {
console.log('Cliente desconectado:', socket.id, reason)
})
})
httpServer.listen(3000)Nginx: proxy para WebSocket
O Nginx precisa dos headers Upgrade e Connection para fazer proxy de WebSocket. Sem eles, a conexão cai silenciosamente para HTTP:
# nginx.conf para WebSocket
server {
listen 443 ssl;
http2 on;
server_name ws.seudominio.com.br;
ssl_certificate /etc/letsencrypt/live/ws.seudominio.com.br/fullchain.pem;
ssl_certificate_key /etc/letsencrypt/live/ws.seudominio.com.br/privkey.pem;
location / {
proxy_pass http://app:3000;
proxy_http_version 1.1;
# Headers obrigatórios para WebSocket
proxy_set_header Upgrade $http_upgrade;
proxy_set_header Connection "upgrade";
proxy_set_header Host $host;
proxy_set_header X-Real-IP $remote_addr;
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
proxy_set_header X-Forwarded-Proto $scheme;
# Timeouts para conexões de longa duração
proxy_read_timeout 3600s; # 1 hora sem mensagem
proxy_send_timeout 3600s;
proxy_connect_timeout 10s;
}
}Redis Adapter: WebSocket com múltiplos workers
Com PM2 cluster ou múltiplos containers, cada worker tem sua própria instância Socket.IO. Um evento emitido no worker 1 não chega aos clientes do worker 2. Redis adapter sincroniza eventos entre workers:
// src/socket.ts
import { Server } from 'socket.io'
import { createAdapter } from '@socket.io/redis-adapter'
import { createClient } from 'redis'
export async function setupSocketIO(httpServer: any) {
const io = new Server(httpServer)
// Criar dois clientes Redis: pub e sub
const pubClient = createClient({ url: process.env.REDIS_URL })
const subClient = pubClient.duplicate()
await Promise.all([pubClient.connect(), subClient.connect()])
// Configurar adapter Redis
io.adapter(createAdapter(pubClient, subClient))
io.on('connection', (socket) => {
// Agora eventos são sincronizados entre todos os workers
socket.on('message', (data) => {
// io.to(room).emit() propaga para clientes em TODOS os workers
io.to(data.room).emit('message', data)
})
})
return io
}Autenticação em WebSocket
WebSocket não tem headers HTTP após o handshake inicial — use token JWT passado na conexão:
// Middleware de autenticação Socket.IO
import jwt from 'jsonwebtoken'
io.use((socket, next) => {
const token = socket.handshake.auth.token
|| socket.handshake.query.token as string
if (!token) {
return next(new Error('Token não fornecido'))
}
try {
const payload = jwt.verify(token, process.env.JWT_SECRET!) as JwtPayload
socket.data.userId = payload.sub
next()
} catch (err) {
next(new Error('Token inválido ou expirado'))
}
})
// No cliente JavaScript:
const socket = io('wss://ws.seudominio.com.br', {
auth: { token: localStorage.getItem('access_token') },
})ws: biblioteca WebSocket nativa (sem Socket.IO)
Para casos simples sem necessidade de salas ou fallback, a biblioteca ws é mais leve que Socket.IO:
// Servidor WebSocket com ws (mais leve que Socket.IO)
import { WebSocketServer, WebSocket } from 'ws'
import { createServer } from 'http'
const server = createServer()
const wss = new WebSocketServer({ server })
const clients = new Set<WebSocket>()
wss.on('connection', (ws, req) => {
clients.add(ws)
ws.on('message', (data) => {
// Broadcast para todos os clientes conectados
for (const client of clients) {
if (client !== ws && client.readyState === WebSocket.OPEN) {
client.send(data.toString())
}
}
})
ws.on('close', () => {
clients.delete(ws)
})
// Ping/pong para manter conexão viva
const pingInterval = setInterval(() => {
if (ws.readyState === WebSocket.OPEN) ws.ping()
}, 30000)
ws.on('close', () => clearInterval(pingInterval))
})
server.listen(3001)$ runstack deploy --plan starter
Não quer configurar manualmente?
Não quer configurar manualmente? Implante o VPS em menos de 3 minutos com a Runstack. Infraestrutura da OPEN DATACENTER, com servidores no Brasil.