Tutoriais

MQTT em JavaScript: tutorial de MQTT.js para Node.js e navegador

Aprenda MQTT em JavaScript com MQTT.js v5: conectar, assinar e publicar no Node.js e no navegador via WebSockets, async/await, React e tratamento de erros.

Atualizado · 8 min de leitura

O MQTT.js é o cliente MQTT mais usado em JavaScript. O mesmo pacote roda no Node.js (sobre TCP, TLS ou WebSockets) e no navegador (sobre WebSockets), suporta MQTT 3.1.1 e 5 e, desde a versão 5, traz tipos TypeScript e métodos baseados em promises. Este tutorial cobre a API da v5 de ponta a ponta.

Instalar o MQTT.js

bash
npm install mqtt

Os exemplos usam ES modules (import) e o sandbox público da HiveMQ. Salve-os como arquivos .mjs ou defina "type": "module" no package.json.

URLs de conexão: qual protocolo em qual lugar

Esquema da URLTransportePorta típicaNode.jsNavegador
mqtt://TCP simples1883SimNão
mqtts://TLS8883SimNão
ws://WebSocket8000, 8083, 80SimSó em páginas http://
wss://WebSocket seguro8884, 8084, 443SimSim

Portas e caminhos WebSocket mudam de broker para broker — confira as páginas da HiveMQ ou do EMQX, ou leia portas MQTT explicadas.

Assinar e publicar no Node.js

A API clássica baseada em eventos: mqtt.connect() retorna um cliente imediatamente e emite eventos à medida que a conexão avança.

client.mjs
import mqtt from 'mqtt';

const topic = 'testmqtt/js-demo/hello';

const client = mqtt.connect('mqtt://broker.hivemq.com:1883', {
  clientId: `js-demo-${Math.random().toString(16).slice(2, 10)}`,
  clean: true,
  keepalive: 60,
  reconnectPeriod: 2000, // ms between reconnect attempts; 0 disables
  connectTimeout: 10_000,
});

client.on('connect', () => {
  console.log('connected');
  client.subscribe('testmqtt/js-demo/#', { qos: 1 }, (err) => {
    if (err) return console.error('subscribe failed:', err.message);
    client.publish(topic, JSON.stringify({ hello: 'world' }), { qos: 1 });
  });
});

client.on('message', (topic, payload, packet) => {
  console.log(`${topic} (retain=${packet.retain}): ${payload.toString()}`);
});

client.on('reconnect', () => console.log('reconnecting...'));
client.on('offline', () => console.log('offline'));
client.on('error', (err) => console.error('error:', err.message));

payload é um Buffer — chame toString() para texto ou JSON.parse(payload.toString()) para JSON. O evento connect dispara de novo após cada reconexão; com clean: true o broker esquece suas assinaturas, e é por isso que assinar dentro do handler é o padrão seguro. O MQTT.js também refaz as assinaturas ao reconectar por padrão (resubscribe: true).

Async/await com connectAsync

Para scripts, testes e funções serverless, a API de promises do MQTT.js v5 fica muito mais natural de ler:

async.mjs
import mqtt from 'mqtt';

const client = await mqtt.connectAsync('mqtts://broker.hivemq.com:8883', {
  protocolVersion: 5,
});

client.on('message', (topic, payload) => {
  console.log(topic, payload.toString());
});

try {
  const granted = await client.subscribeAsync('testmqtt/js-demo/#', { qos: 1 });
  console.log('granted:', granted.map((g) => `${g.topic} QoS ${g.qos}`).join(', '));

  await client.publishAsync('testmqtt/js-demo/async', 'sent with publishAsync', {
    qos: 1,
    retain: false,
  });

  await new Promise((resolve) => setTimeout(resolve, 2000));
} finally {
  await client.endAsync();
}

connectAsync() rejeita quando o cliente emite um error antes de conectar — por exemplo, quando o broker rejeita suas credenciais —, então você pode tratar isso com um try/catch normal. Para um host inacessível, ele continua tentando por padrão; passe false como terceiro argumento para falhar rápido. publishAsync() resolve assim que o broker confirma uma mensagem QoS 1 ou 2. Veja níveis de QoS do MQTT para saber o que cada nível garante.

Veja suas mensagens do MQTT.js ao vivo

TLS, credenciais e opções do MQTT 5 no Node.js

Brokers de produção exigem TLS e autenticação. As credenciais vão nas opções de conexão; para um broker com certificado publicamente confiável, mqtts:// é tudo de que você precisa. Para uma CA privada, passe o certificado da CA (e, para mutual TLS, um certificado e uma chave de cliente):

secure.mjs
import { readFileSync } from 'node:fs';
import mqtt from 'mqtt';

const client = await mqtt.connectAsync('mqtts://broker.example.com:8883', {
  clientId: 'billing-service-01',
  username: process.env.MQTT_USERNAME,
  password: process.env.MQTT_PASSWORD,
  protocolVersion: 5,
  clean: false,
  properties: { sessionExpiryInterval: 3600 },
  // Only for a private CA / mutual TLS:
  // ca: readFileSync('ca.crt'),
  // cert: readFileSync('client.crt'),
  // key: readFileSync('client.key'),
  will: {
    topic: 'services/billing/status',
    payload: Buffer.from('offline'),
    qos: 1,
    retain: true,
  },
});

await client.publishAsync('services/billing/status', 'online', { qos: 1, retain: true });
await client.publishAsync('orders/created', JSON.stringify({ id: 42 }), {
  qos: 1,
  properties: {
    contentType: 'application/json',
    messageExpiryInterval: 60,
    userProperties: { source: 'billing' },
  },
});

Nunca defina rejectUnauthorized: false fora de testes locais — isso desliga a verificação de certificados. Mantenha as credenciais em variáveis de ambiente, não no código-fonte. A opção will registra um Last Will que o broker publica se o serviço morrer sem desconectar, e retain: true mantém o status mais recente disponível para novos assinantes (veja mensagens retidas (retained)). Se você realmente precisa das propriedades do MQTT 5 é assunto de MQTT 5 vs 3.1.1.

Encerrando de forma limpa

Um serviço Node.js de longa duração deve desconectar de forma elegante para que as mensagens QoS 1/2 em trânsito terminem e o broker não dispare seu Last Will em um restart normal:

javascript
process.on('SIGTERM', async () => {
  await client.publishAsync('services/billing/status', 'offline', { qos: 1, retain: true });
  await client.endAsync();
  process.exit(0);
});

MQTT.js no navegador

No navegador, só URLs WebSocket funcionam. Em uma página HTTPS você precisa usar wss://; caso contrário, o navegador bloqueia a conexão como mixed content:

browser.js
import mqtt from 'mqtt';

const client = mqtt.connect('wss://broker.hivemq.com:8884/mqtt', {
  clientId: `web-${crypto.randomUUID().slice(0, 8)}`,
});

client.on('connect', () => client.subscribe('testmqtt/js-demo/#'));
client.on('message', (topic, payload) => {
  const li = document.createElement('li');
  li.textContent = `${topic}: ${payload.toString()}`;
  document.querySelector('#messages').append(li);
});

Bundlers modernos como Vite, webpack 5 e o compilador do Next.js escolhem o build para navegador quando você faz import mqtt from 'mqtt'. Se um bundler reclamar de módulos nativos do Node, importe o bundle pré-compilado de mqtt/dist/mqtt.min.js. Sem bundler, carregue esse mesmo arquivo de uma CDN com uma tag <script> e use o objeto global mqtt. O guia de MQTT sobre WebSockets aborda caminhos e portas em detalhes.

Usando MQTT.js no React

O bug mais comum é criar um novo cliente a cada render. Crie o cliente uma única vez dentro do useEffect e sempre o encerre no cleanup — o StrictMode do React monta os efeitos duas vezes em desenvolvimento e, sem cleanup, você ganha conexões e mensagens duplicadas:

useMqtt.ts
import { useEffect, useState } from 'react';
import mqtt from 'mqtt';

export function useMqttMessages(url: string, topic: string) {
  const [messages, setMessages] = useState<string[]>([]);

  useEffect(() => {
    const client = mqtt.connect(url);
    client.on('connect', () => client.subscribe(topic));
    client.on('message', (_t, payload) => {
      setMessages((prev) => [...prev.slice(-99), payload.toString()]);
    });
    return () => {
      client.end();
    };
  }, [url, topic]);

  return messages;
}

No Next.js, o MQTT.js usa APIs do navegador, então chame-o apenas a partir de Client Components ('use client') ou de route handlers no servidor usando mqtt:// ou mqtts://. Em aplicações maiores, coloque um único cliente em um context provider e compartilhe-o.

Tratamento de erros

  • Sempre registre um listener de error. No Node.js, um evento error não tratado derruba o processo.
  • Credenciais erradas continuam tentando. Com a API de eventos, um CONNECT rejeitado emite error e o MQTT.js continua reconectando. Inspecione err.code e chame client.end() se não fizer sentido tentar de novo. Consulte os códigos com a ferramenta de reason codes MQTT.
  • Loops infinitos de reconexão com desconexão imediata geralmente indicam um client ID duplicado ou uma ACL rejeitando sua assinatura. Veja o guia de erros de conexão MQTT.
  • Buffer offline. Enquanto desconectado, o MQTT.js enfileira as publicações em memória e as envia ao reconectar. Defina queueQoSZero: false se dados QoS 0 desatualizados devem ser descartados.

Precisa de um ponto de partida para o seu próprio broker? O gerador de código MQTT gera este código para o seu host, porta, credenciais e tópico — depois teste ao vivo no cliente MQTT online.

Perguntas frequentes

O MQTT.js consegue se conectar à porta 1883 a partir do navegador?

Não. Navegadores não conseguem abrir sockets TCP brutos, então o MQTT.js no navegador precisa usar uma URL ws:// ou wss:// apontando para o listener WebSocket do broker, por exemplo wss://broker.hivemq.com:8884/mqtt. No Node.js você pode usar mqtt://, mqtts://, ws:// ou wss://.

Como uso async/await com o MQTT.js?

O MQTT.js v5 oferece métodos baseados em promises: mqtt.connectAsync() para conectar e client.subscribeAsync(), client.publishAsync() e client.endAsync() no cliente retornado. As mensagens recebidas continuam chegando pelo evento message.

Por que meu app React abre duas conexões MQTT?

O StrictMode do React monta os componentes duas vezes em desenvolvimento, então um efeito que conecta sem cleanup cria dois clientes. Crie o cliente dentro do useEffect e chame client.end() na função de cleanup, ou mantenha um único cliente em um módulo ou context.