Tutoriales

MQTT en JavaScript: tutorial de MQTT.js para Node.js y el navegador

Aprende MQTT en JavaScript con MQTT.js v5: conecta, suscríbete y publica en Node.js y en el navegador por WebSockets, async/await, React y manejo de errores.

Actualizado · 8 min de lectura

MQTT.js es el cliente MQTT para JavaScript más usado. El mismo paquete funciona en Node.js (sobre TCP, TLS o WebSockets) y en el navegador (sobre WebSockets), soporta MQTT 3.1.1 y 5, y desde la versión 5 incluye tipos de TypeScript y métodos basados en promesas. Este tutorial cubre la API de la v5 de principio a fin.

Instalar MQTT.js

bash
npm install mqtt

Los ejemplos usan módulos ES (import) y el sandbox público de HiveMQ. Guárdalos como archivos .mjs o pon "type": "module" en package.json.

URLs de conexión: qué protocolo usar en cada sitio

Esquema de URLTransportePuerto típicoNode.jsNavegador
mqtt://TCP sin cifrar1883SíNo
mqtts://TLS8883SíNo
ws://WebSocket8000, 8083, 80SíSolo en páginas http://
wss://WebSocket seguro8884, 8084, 443SíSí

Los puertos y las rutas WebSocket cambian según el broker — consulta las páginas de HiveMQ o EMQX, o lee los puertos MQTT explicados.

Suscribirse y publicar en Node.js

La API clásica basada en eventos: mqtt.connect() devuelve un cliente de inmediato y emite eventos a medida que avanza la conexión.

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 es un Buffer — llama a toString() para texto o a JSON.parse(payload.toString()) para JSON. El evento connect se dispara de nuevo tras cada reconexión; con clean: true el broker olvida tus suscripciones, por eso suscribirse dentro del handler es el patrón seguro. MQTT.js además se vuelve a suscribir al reconectar por defecto (resubscribe: true).

Async/await con connectAsync

Para scripts, tests y funciones serverless, la API de promesas de MQTT.js v5 se lee de forma mucho más natural:

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() se rechaza cuando el cliente emite un error antes de conectarse — por ejemplo, cuando el broker rechaza tus credenciales — así que puedes gestionarlo con un try/catch normal. Con un host inaccesible, por defecto sigue reintentando; pasa false como tercer argumento para que falle rápido. publishAsync() se resuelve cuando el broker confirma un mensaje QoS 1 o 2. Consulta los niveles de QoS de MQTT para ver qué garantiza cada nivel.

Mira tus mensajes de MQTT.js en directo

TLS, credenciales y opciones de MQTT 5 en Node.js

Los brokers de producción exigen TLS y autenticación. Las credenciales van en las opciones de conexión; para un broker con un certificado de confianza pública, mqtts:// es todo lo que necesitas. Para una CA privada, pasa el certificado de la CA (y, para TLS mutuo, un certificado y una clave 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 pongas rejectUnauthorized: false fuera de pruebas locales — desactiva la verificación de certificados. Guarda las credenciales en variables de entorno, no en el código fuente. La opción will registra un Last Will que el broker publica si el servicio muere sin desconectarse, y retain: true mantiene el último estado disponible para los nuevos suscriptores (consulta los mensajes retenidos). Si realmente necesitas las propiedades de MQTT 5 se explica en MQTT 5 vs 3.1.1.

Cerrar de forma limpia

Un servicio Node.js de larga duración debería desconectarse ordenadamente para que terminen los mensajes QoS 1/2 en curso y el broker no dispare tu Last Will en un reinicio 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 en el navegador

En el navegador solo funcionan las URLs WebSocket. En una página HTTPS debes usar wss://; si no, el navegador bloquea la conexión como contenido mixto:

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);
});

Los bundlers modernos como Vite, webpack 5 y el compilador de Next.js eligen la build para navegador cuando haces import mqtt from 'mqtt'. Si un bundler se queja de los módulos integrados de Node, importa en su lugar el bundle precompilado de mqtt/dist/mqtt.min.js. Sin bundler, carga ese mismo archivo desde un CDN con una etiqueta <script> y usa el objeto global mqtt. La guía de MQTT sobre WebSockets trata rutas y puertos en profundidad.

Usar MQTT.js en React

El bug más común es crear un cliente nuevo en cada render. Crea el cliente una sola vez dentro de useEffect y ciérralo siempre en la función de limpieza — React StrictMode monta los efectos dos veces en desarrollo, y sin limpieza obtienes conexiones y mensajes duplicados:

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;
}

En Next.js, MQTT.js usa APIs del navegador, así que llámalo solo desde Client Components ('use client') o desde route handlers del servidor usando mqtt:// o mqtts://. En aplicaciones más grandes, pon un único cliente en un context provider y compártelo.

Gestión de errores

  • Añade siempre un listener de error. En Node.js, un evento error sin gestionar hace caer el proceso.
  • Las credenciales incorrectas siguen reintentando. Con la API de eventos, un CONNECT rechazado emite error y MQTT.js sigue reconectando. Revisa err.code y llama a client.end() si reintentar no tiene sentido. Consulta los códigos con la herramienta de códigos de motivo MQTT.
  • Los bucles de reconexión infinitos con una desconexión inmediata suelen indicar un client ID duplicado o una ACL que rechaza tu suscripción. Consulta la guía de errores de conexión MQTT.
  • Buffer sin conexión. Mientras está desconectado, MQTT.js encola las publicaciones en memoria y las envía al reconectar. Pon queueQoSZero: false si prefieres descartar los datos QoS 0 obsoletos.

¿Necesitas un punto de partida para tu propio broker? El generador de código MQTT genera este código para tu host, puerto, credenciales y topic — y luego puedes probarlo en directo en el cliente MQTT online.

Preguntas frecuentes

¿Puede MQTT.js conectarse al puerto 1883 desde el navegador?

No. Los navegadores no pueden abrir sockets TCP directos, así que MQTT.js en el navegador debe usar una URL ws:// o wss:// que apunte al listener WebSocket del broker, por ejemplo wss://broker.hivemq.com:8884/mqtt. En Node.js puedes usar mqtt://, mqtts://, ws:// o wss://.

¿Cómo uso async/await con MQTT.js?

MQTT.js v5 ofrece métodos basados en promesas: mqtt.connectAsync() para conectar, y client.subscribeAsync(), client.publishAsync() y client.endAsync() en el cliente devuelto. Los mensajes entrantes se siguen entregando a través del evento message.

¿Por qué mi app de React abre dos conexiones MQTT?

React StrictMode monta los componentes dos veces en desarrollo, así que un efecto que se conecta sin función de limpieza crea dos clientes. Crea el cliente dentro de useEffect y llama a client.end() en la limpieza, o mantén un único cliente en un módulo o un context.