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
npm install mqttLos 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 URL | Transporte | Puerto típico | Node.js | Navegador |
|---|---|---|---|---|
mqtt:// | TCP sin cifrar | 1883 | Sí | No |
mqtts:// | TLS | 8883 | Sí | No |
ws:// | WebSocket | 8000, 8083, 80 | Sí | Solo en páginas http:// |
wss:// | WebSocket seguro | 8884, 8084, 443 | Sí | 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.
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:
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):
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:
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:
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:
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 eventoerrorsin gestionar hace caer el proceso. - Las credenciales incorrectas siguen reintentando. Con la API de eventos, un CONNECT rechazado emite
errory MQTT.js sigue reconectando. Revisaerr.codey llama aclient.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: falsesi 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.