Rodar MQTT com JavaScript no navegador é a forma mais rápida de construir um dashboard ao vivo, um painel de controle remoto para uma estação de saneamento ou uma ferramenta de depuração sem escrever um backend. Neste tutorial você vai criar uma página HTML autocontida que se conecta ao servidor MQTT público broker.connectmq.com via WebSocket seguro, assina um topic, publica mensagens e as exibe em tempo real. Você também vai entender por que os navegadores precisam de WebSocket para MQTT, por que o CORS não é um problema e como migrar a mesma página para um broker privado com segurança.
Por que um Navegador Precisa de MQTT sobre WebSocket
Uma página web não consegue abrir sockets TCP brutos. A sandbox do navegador só permite que o JavaScript acesse a rede por HTTP(S), WebSocket, WebRTC e algumas APIs mais recentes. O MQTT padrão roda sobre TCP na porta 1883 (ou TLS na 8883), então um navegador não consegue alcançar essas portas diretamente.
A especificação MQTT resolve isso permitindo que os pacotes MQTT sejam transportados dentro de frames binários de WebSocket. O broker expõe um listener extra, o navegador abre um WebSocket para ele e, daí em diante, a troca é MQTT comum: CONNECT, SUBSCRIBE, PUBLISH, PINGREQ. Se quiser os detalhes no nível do protocolo, nosso guia de MQTT sobre WebSocket percorre o handshake e o cabeçalho de subprotocolo.
O ConnectMQ expõe dois listeners WebSocket em todo broker:
| Endpoint | URL | Quando usar |
|---|---|---|
| WebSocket | ws://broker.connectmq.com:8083/mqtt |
Apenas testes locais a partir de uma página http:// |
| WebSocket seguro | wss://broker.connectmq.com:8084/mqtt |
Qualquer página servida por https:// e todo uso em produção |
Use wss:// por padrão. Uma página carregada por HTTPS não pode abrir uma conexão ws:// sem criptografia; o navegador a bloqueia como conteúdo misto antes que qualquer pacote saia da máquina.
E o CORS?
O CORS (Cross-Origin Resource Sharing) se aplica a fetch e XMLHttpRequest. Conexões WebSocket não passam por verificações de preflight do CORS. O navegador envia um cabeçalho Origin durante o upgrade, e um servidor pode optar por inspecioná-lo, mas não existe a negociação com Access-Control-Allow-Origin. É por isso que você pode abrir o arquivo HTML abaixo a partir da sua área de trabalho, de um servidor de desenvolvimento local ou de qualquer domínio, e ele se conecta sem truques de proxy.
Escolhendo uma Biblioteca: MQTT.js
O MQTT.js é o cliente MQTT de fato para JavaScript. O mesmo pacote roda no Node.js e nos navegadores, suporta MQTT 3.1.1 e MQTT 5 e cuida de reconexões, keepalive e dos fluxos de QoS 1 e 2 para você. A versão 5 traz um bundle pronto para navegador, então você pode carregá-lo de uma CDN com uma única tag script. A documentação do MQTT.js no GitHub lista todas as opções; o subconjunto abaixo é tudo de que um cliente mínimo precisa.
Se depois você migrar para um bundler ou framework, o código quase não muda. Nosso guia de MQTT.js com Node.js cobre o lado do servidor, e o tutorial de MQTT com React mostra como envolver o mesmo cliente em hooks.
O Cliente MQTT Completo com JavaScript no Navegador em Uma Página
Salve o código a seguir como mqtt-client.html e abra-o em qualquer navegador moderno. Ele carrega o MQTT.js de uma CDN, gera um client ID único e um prefixo de topic aleatório e oferece botões para conectar, assinar e publicar.
<!doctype html>
<html lang="en">
<head>
<meta charset="utf-8">
<meta name="viewport" content="width=device-width, initial-scale=1">
<title>Minimal MQTT Browser Client</title>
<script src="https://unpkg.com/mqtt@5.10.1/dist/mqtt.min.js"></script>
<style>
body { font-family: system-ui, sans-serif; max-width: 720px; margin: 2rem auto; padding: 0 1rem; }
input, button { font: inherit; padding: .4rem .6rem; }
#log { border: 1px solid #ccc; height: 280px; overflow-y: auto; padding: .5rem; font-family: monospace; font-size: .85rem; }
.row { display: flex; gap: .5rem; margin: .5rem 0; }
.row input { flex: 1; }
</style>
</head>
<body>
<h1>MQTT in the Browser</h1>
<p>Status: <strong id="status">disconnected</strong></p>
<div class="row">
<input id="url" value="wss://broker.connectmq.com:8084/mqtt">
<button id="connectBtn">Connect</button>
<button id="disconnectBtn">Disconnect</button>
</div>
<div class="row">
<input id="subTopic">
<button id="subBtn">Subscribe</button>
</div>
<div class="row">
<input id="pubTopic">
<input id="pubPayload" value='{"temperature": 21.5}'>
<button id="pubBtn">Publish</button>
</div>
<div id="log"></div>
<script>
const randomId = Math.random().toString(16).slice(2, 10);
const prefix = `connectmq/demo/${randomId}`;
document.getElementById("subTopic").value = `${prefix}/#`;
document.getElementById("pubTopic").value = `${prefix}/sensors/room1`;
const statusEl = document.getElementById("status");
const logEl = document.getElementById("log");
let client = null;
function log(text) {
const line = document.createElement("div");
line.textContent = `[${new Date().toLocaleTimeString()}] ${text}`;
logEl.appendChild(line);
logEl.scrollTop = logEl.scrollHeight;
}
document.getElementById("connectBtn").onclick = () => {
if (client) return;
const url = document.getElementById("url").value;
client = mqtt.connect(url, {
clientId: `browser-${randomId}`,
protocolVersion: 5,
clean: true,
keepalive: 30,
connectTimeout: 10000,
reconnectPeriod: 3000
// Para um broker privado do ConnectMQ, adicione:
// username: "YOUR_USERNAME",
// password: "YOUR_PASSWORD"
});
client.on("connect", () => { statusEl.textContent = "connected"; log(`Connected to ${url}`); });
client.on("reconnect", () => { statusEl.textContent = "reconnecting"; });
client.on("close", () => { statusEl.textContent = "disconnected"; });
client.on("offline", () => log("Client went offline"));
client.on("error", (err) => log(`Error: ${err.message}`));
client.on("message", (topic, payload, packet) => {
const retained = packet.retain ? " (retained)" : "";
log(`${topic}${retained}: ${payload.toString()}`);
});
};
document.getElementById("disconnectBtn").onclick = () => {
if (!client) return;
client.end(false, () => log("Disconnected cleanly"));
client = null;
};
document.getElementById("subBtn").onclick = () => {
if (!client) return log("Connect first");
const topic = document.getElementById("subTopic").value;
client.subscribe(topic, { qos: 1 }, (err, granted) => {
if (err) return log(`Subscribe failed: ${err.message}`);
granted.forEach(g => log(`Subscribed to ${g.topic} (granted QoS ${g.qos})`));
});
};
document.getElementById("pubBtn").onclick = () => {
if (!client) return log("Connect first");
const topic = document.getElementById("pubTopic").value;
const payload = document.getElementById("pubPayload").value;
client.publish(topic, payload, { qos: 1 }, (err) => {
if (err) log(`Publish failed: ${err.message}`);
});
};
</script>
</body>
</html>
Clique em Connect, depois em Subscribe e depois em Publish. A mensagem vai da sua página até o broker e volta direto para a mesma página, porque você assinou prefix/#, que corresponde ao topic de publicação.
Como o Código Funciona
Conectando
mqtt.connect() retorna um cliente imediatamente e se conecta em segundo plano. Os eventos informam o que acontece em seguida: connect após um CONNACK bem-sucedido, reconnect a cada nova tentativa, close quando o socket fecha e error para problemas como um login rejeitado. Definir protocolVersion: 5 oferece recursos do MQTT 5, como reason codes e user properties; remova a opção para voltar ao 3.1.1.
O client ID é gerado a cada carregamento da página. O servidor MQTT público aceita qualquer client ID não vazio, mas dois clientes com o mesmo ID derrubam um ao outro em loop, o que parece uma conexão instável. IDs aleatórios evitam isso quando você abre a página em duas abas.
Assinando e recebendo
O evento message é disparado a cada PUBLISH recebido. payload é um buffer de bytes, então chame toString() para texto ou JSON.parse(payload.toString()) para JSON. O terceiro argumento é o pacote bruto, que expõe retain, qos e, no MQTT 5, properties.
Repare no uso de textContent na função log. Qualquer pessoa pode publicar no servidor MQTT público, então trate todo payload como entrada não confiável. Inseri-lo com innerHTML permitiria que uma mensagem maliciosa executasse scripts na sua página.
Publicando
client.publish(topic, payload, options, callback) coloca a mensagem na fila. Com QoS 1, o callback roda depois que o broker a confirma com PUBACK. Se a conexão estiver caída, o MQTT.js mantém as mensagens QoS 1 e 2 em seu armazenamento de saída e as envia após reconectar, desde que a página continue aberta. O guia de níveis de QoS explica quando essa garantia importa.
Testando pelo Outro Lado
Para provar que as mensagens realmente passam pelo broker, acompanhe-as em um terminal enquanto clica em Publish na página. Substitua o prefixo pelo que sua página mostra:
mosquitto_sub -h broker.connectmq.com -p 1883 -i cli-watch-7f3a -t 'connectmq/demo/YOUR_PREFIX/#' -v
Publique a partir do terminal e o log do navegador se atualiza na hora:
mosquitto_pub -h broker.connectmq.com -p 1883 -i cli-pub-7f3a -t 'connectmq/demo/YOUR_PREFIX/sensors/room1' -m '{"temperature": 23.1}'
Essa ida e volta, do navegador para um cliente TCP e de volta, é a demonstração mais clara de que um cliente WebSocket e um cliente TCP nativo compartilham o mesmo espaço de topics no broker.
Checklist de Solução de Problemas
Quando o status nunca chega a "connected", percorra esta lista em ordem:
- Caminho errado. O ConnectMQ serve WebSocket em
/mqtt. Uma URL sem o caminho falha durante o upgrade HTTP. - Conteúdo misto. Uma página
https://não pode abrirws://. Usewss://na porta 8084. - Porta bloqueada. Algumas redes corporativas só permitem a porta 443. Teste a partir de outra rede ou do roteador de um celular para descartar essa hipótese.
- Client ID duplicado. Se o log mostra connect e close se repetindo a cada poucos segundos, outro cliente está usando o mesmo ID.
- Credenciais vazias ou inválidas. Em um broker privado, um nome de usuário ou senha errados geram um evento
errorcom "Not authorized" ou "Bad username or password". Confira os valores no portal. - Limites do servidor MQTT público. Ele permite 10 mensagens por segundo por cliente, 10 assinaturas por cliente, 64 KB por mensagem e 8 níveis de topic. Um botão ligado a um loop rápido vai atingir o limite de taxa.
- DevTools. Abra a aba Network, filtre por WS e inspecione os frames. Você verá os pacotes MQTT binários e o código de resposta do upgrade.
Migrando do Servidor MQTT Público para um Broker Privado
O servidor MQTT público é perfeito para este tutorial, mas todo topic dele pode ser lido por qualquer pessoa que o adivinhe, então ele serve apenas para testes e protótipos. Para projetos reais, crie seu próprio broker:
- Cadastre-se em app.connectmq.com e escolha o plano Serverless gratuito.
- Crie um broker e copie o nome de usuário, a senha e o client ID gerados.
- Troque a URL pelo endpoint WebSocket seguro do seu broker na porta 8084 com o caminho
/mqtte definausername,passwordeclientIdnas opções.
Tenha cuidado com o lugar onde essas credenciais ficam. Qualquer coisa que você coloca no JavaScript do navegador pode ser lida por quem abrir o DevTools. Bons padrões são:
- Use credenciais separadas e com poucos privilégios para dashboards no navegador, nunca as que seus dispositivos usam.
- Peça ao usuário que digite as credenciais em tempo de execução, em vez de deixá-las fixas no código da página.
- Em sites públicos, deixe um backend fazer as publicações privilegiadas e mantenha o navegador apenas para leitura. O artigo sobre padrões de MQTT com Next.js mostra essa divisão.
O guia de boas práticas de segurança MQTT aprofunda o tratamento de credenciais e o design de topics.
Próximos Passos
Esta página única é uma base que você pode expandir em algumas direções:
- Gráficos ao vivo. Faça o parse dos payloads JSON e envie os valores para uma biblioteca de gráficos. Mantenha um array de tamanho fixo para que a memória não cresça em sessões longas.
- Chat e presença. Retained messages e o Last Will facilitam mostrar quem está online. O tutorial de app de chat com MQTT constrói exatamente isso.
- Código tipado. Se a página crescer, migre para TypeScript e um bundler; o guia de MQTT com TypeScript cobre a tipagem de payloads e eventos.
- Outras linguagens. A visão geral de bibliotecas cliente MQTT compara o MQTT.js com clientes Python, Go, Java e C#, caso seu backend use outra stack.
Comece a Usar o ConnectMQ
O ConnectMQ oferece um broker MQTT gerenciado completo, com WebSocket seguro na porta 8084 já configurado, um certificado Let's Encrypt e, no portal, um cliente MQTT no navegador e um explorador de topics ao vivo para conferir o que sua página publica. Comece hoje no servidor MQTT público (gratuito) e depois crie um broker MQTT na nuvem privado quando estiver pronto. Crie uma conta gratuita no plano Serverless, sem cartão de crédito.
Perguntas frequentes
Um navegador pode se conectar ao MQTT na porta 1883?
Não. Navegadores não conseguem abrir conexões TCP brutas, então precisam usar MQTT sobre WebSocket. No ConnectMQ, isso é a porta 8083 para ws:// e a porta 8084 para wss://, ambas no caminho /mqtt.
Preciso de um backend ou proxy para usar MQTT no navegador?
Não. A página se conecta diretamente ao broker via WebSocket, e o WebSocket não passa pelo preflight do CORS. Um backend só é útil quando você quer manter credenciais privilegiadas fora do cliente.
O MQTT.js é a única biblioteca MQTT em JavaScript para navegadores?
Ele é a opção mais usada e mantida ativamente, com suporte a MQTT 5 e um bundle pronto para navegador. O antigo cliente JavaScript do Paho ainda funciona, mas deixou de ser a escolha comum para novos projetos.
Por que meu cliente no navegador fica reconectando a cada poucos segundos?
A causa mais comum são dois clientes compartilhando o mesmo client ID, o que faz o broker desconectar o mais antigo a cada vez. Gere um ID aleatório por aba e verifique também se você não está excedendo os limites de taxa do servidor MQTT público.



