Tutoriales de IoT

Tutorial de MQTT.js en Node.js: publicar, suscribirse, QoS y TLS

Crea un publicador y un suscriptor en Node.js con MQTT.js v5, usa la API asíncrona, QoS y Last Will, cifra con TLS en el puerto 8883 y ajusta las opciones de reconexión para que tu servicio sobreviva a los cortes de red.

Salida de terminal de Node.js de un publicador y un suscriptor de MQTT.js en Node.js conectados a un broker en la nube mediante TLS
Pruébalo en tu propio broker MQTTConsigue un broker MQTT privado y gestionado con el plan Free y empieza con ejemplos de código listos para usar.
Pruébalo gratis Ejemplos de código Sin tarjeta

MQTT.js es el cliente MQTT estándar para JavaScript, y usar MQTT.js en Node.js es la forma más rápida de crear backends IoT, puentes y herramientas de prueba. En este tutorial crearás un publicador y un suscriptor en Node.js con MQTT.js v5, usarás su API basada en promesas, elegirás niveles de QoS, añadirás un Last Will, pasarás a TLS y configurarás las reconexiones para un servicio de larga duración. Todos los ejemplos se ejecutan contra el servidor MQTT público gratuito broker.connectmq.com, así que solo necesitas tener Node.js instalado.

Requisitos previos

  • Node.js 18 o posterior (node --version)
  • npm, pnpm o yarn
  • Opcional: mosquitto_sub para ver el tráfico desde un segundo terminal

Crea un proyecto e instala la librería:

mkdir mqtt-node-demo && cd mqtt-node-demo
npm init -y
npm pkg set type=module
npm install mqtt

Establecer "type": "module" te permite usar la sintaxis import y await de nivel superior. MQTT.js v5 está escrito en TypeScript e incluye sus propias definiciones de tipos, así que los editores te ofrecen autocompletado sin paquetes adicionales.

URLs de conexión y puertos

MQTT.js elige el transporte a partir del esquema de la URL. En los brokers de ConnectMQ los puertos son:

Esquema Puerto Transporte Uso típico en Node.js
mqtt:// 1883 TCP sin cifrar Solo pruebas locales
mqtts:// 8883 TCP con TLS Servicios en producción
ws:// 8083 WebSocket Rara vez necesario en Node.js
wss:// 8084 (ruta /mqtt) WebSocket seguro Navegadores, proxies restrictivos

En Node.js, prefiere mqtts:// en el 8883. WebSocket importa en los navegadores, que no pueden abrir sockets TCP directos; el artículo MQTT sobre WebSocket cubre esa parte.

Tu primer suscriptor con MQTT.js

Guárdalo como subscriber.js:

import mqtt from "mqtt";

const PREFIX = "connectmq/demo/node-a71c";
const clientId = `node-sub-${Math.random().toString(16).slice(2, 10)}`;

const client = mqtt.connect("mqtts://broker.connectmq.com:8883", {
  clientId,
  protocolVersion: 5,
  clean: true,
  keepalive: 30,
  reconnectPeriod: 2000,   // espera 2 s entre intentos de reconexión
  connectTimeout: 10000,   // abandona un intento individual tras 10 s
});

client.on("connect", async (connack) => {
  console.log(`Connected as ${clientId}, session present: ${connack.sessionPresent}`);
  const granted = await client.subscribeAsync(`${PREFIX}/+/telemetry`, { qos: 1 });
  console.log("Subscribed:", granted.map((g) => `${g.topic} (qos ${g.qos})`).join(", "));
});

client.on("message", (topic, payload, packet) => {
  let data;
  try {
    data = JSON.parse(payload.toString());
  } catch {
    data = payload.toString();
  }
  const device = topic.split("/").at(-2);
  console.log(`[${device}] qos=${packet.qos} retain=${packet.retain}`, data);
});

client.on("reconnect", () => console.log("Reconnecting..."));
client.on("offline", () => console.log("Offline"));
client.on("error", (err) => console.error("MQTT error:", err.message));

process.on("SIGINT", async () => {
  await client.endAsync();
  process.exit(0);
});

Suscribirse dentro del handler connect es intencionado. Con clean: true, el broker olvida las suscripciones cuando se cae la conexión, así que volver a suscribirse en cada conexión garantiza que sigas recibiendo mensajes tras una reconexión.

Publicar con QoS y un Last Will

Guárdalo como publisher.js:

import mqtt from "mqtt";

const PREFIX = "connectmq/demo/node-a71c";
const DEVICE = "sensor-01";
const clientId = `node-pub-${Math.random().toString(16).slice(2, 10)}`;

const client = await mqtt.connectAsync("mqtts://broker.connectmq.com:8883", {
  clientId,
  protocolVersion: 5,
  will: {
    topic: `${PREFIX}/${DEVICE}/status`,
    payload: Buffer.from("offline"),
    qos: 1,
    retain: true,
  },
});

console.log("Connected");
await client.publishAsync(`${PREFIX}/${DEVICE}/status`, "online", { qos: 1, retain: true });

let count = 0;
const timer = setInterval(async () => {
  const reading = {
    temperature: +(20 + Math.random() * 5).toFixed(2),
    humidity: +(40 + Math.random() * 10).toFixed(1),
    ts: Date.now(),
  };
  try {
    await client.publishAsync(`${PREFIX}/${DEVICE}/telemetry`, JSON.stringify(reading), {
      qos: 1,
      properties: { messageExpiryInterval: 60, contentType: "application/json" },
    });
    console.log("Published", reading);
  } catch (err) {
    console.error("Publish failed:", err.message);
  }
  if (++count === 20) {
    clearInterval(timer);
    await client.publishAsync(`${PREFIX}/${DEVICE}/status`, "offline", { qos: 1, retain: true });
    await client.endAsync();
  }
}, 2000);

Ejecuta ambos:

node subscriber.js
# en un segundo terminal
node publisher.js

Aquí ocurren varias cosas:

  • connectAsync se resuelve en cuanto el broker envía el CONNACK, lo que resulta cómodo en scripts. En servicios de larga duración, el evento connect sigue siendo útil porque se dispara de nuevo tras cada reconexión.
  • QoS 1 significa que el broker confirma cada publicación, y publishAsync solo se resuelve después de esa confirmación. Para telemetría de alta frecuencia en la que una pérdida ocasional no importa, QoS 0 es más barato. Los compromisos se explican en los niveles de QoS de MQTT.
  • El Last Will lo guarda el broker al conectar y lo publica si el cliente desaparece sin una desconexión limpia, de modo que otros servicios saben que el sensor está offline. Retenerlo hace que los nuevos suscriptores vean el estado actual al instante.
  • Las propiedades de MQTT 5 como messageExpiryInterval indican al broker que descarte el mensaje si no puede entregarse en 60 segundos, lo que evita que se acumulen lecturas obsoletas para suscriptores desconectados.

El área pública del servidor público la puede leer cualquiera, así que cambia la parte node-a71c del prefijo por tu propia cadena aleatoria y nunca envíes datos reales allí.

Opciones de reconexión que importan

MQTT.js se reconecta automáticamente. Estas opciones deciden cómo se comporta:

Opción Valor por defecto Recomendación
reconnectPeriod 1000 ms De 2000 a 5000 ms en servicios; 0 desactiva las reconexiones
connectTimeout 30000 ms 10000 ms para detectar antes una red caída
keepalive 60 s De 30 a 60 s; un valor menor detecta antes las caídas silenciosas
clean true false con un client ID estable si necesitas mensajes en cola
properties.sessionExpiryInterval 0 Segundos que el broker conserva la sesión tras la desconexión (MQTT 5)
queueQoSZero true Ponlo en false si no deben enviarse mensajes QoS 0 obsoletos tras reconectar

Para un servicio que no puede perder mensajes mientras está brevemente offline, combina un client ID fijo, clean: false, suscripciones QoS 1 y un intervalo de expiración de sesión. El broker encola entonces los mensajes de la sesión y los entrega cuando el cliente vuelve.

Estructurar un servicio Node.js de larga duración

Los scripts anteriores son cortos a propósito. Un servicio que funciona durante meses necesita algo más de estructura, y casi toda tiene que ver con el ciclo de vida más que con MQTT en sí:

  • Crea un cliente por proceso y compártelo mediante un módulo. Abrir una conexión nueva por petición o por tarea desperdicia recursos y alcanza rápidamente los límites de conexiones.
  • Trata connect como "listo". Condiciona el trabajo que necesita el broker a la propiedad connected del cliente y decide qué hacer cuando es falsa: almacenar en búfer, descartar o devolver un error a quien llama.
  • Gestiona las señales de apagado. Al recibir SIGTERM (enviada por Docker, Kubernetes y systemd), publica un estado final si lo usas, llama a endAsync() para que se vacíen los mensajes QoS 1 en curso y solo entonces termina.
  • Registra las transiciones de estado, no cada mensaje. Los eventos connect, reconnect, offline, close y error son lo que necesitas al depurar una caída. Los logs por mensaje los ahogan.
  • Mantén los handlers rápidos. El evento message se ejecuta en el event loop de Node. El trabajo pesado, como las escrituras en base de datos, debe esperarse con cuidado o enviarse a una cola para que un sistema lento aguas abajo no retrase todos los demás mensajes.

Estos hábitos importan más que el valor de cualquier opción concreta, y se aplican por igual a puentes, workers y herramientas de prueba construidos sobre MQTT.js.

Autenticarse con un broker privado

En producción, usa las credenciales de tu broker. Con ConnectMQ, creas un broker en el portal y recibes un usuario, una contraseña y un client ID. Cárgalos desde variables de entorno:

import mqtt from "mqtt";

const client = mqtt.connect(`mqtts://${process.env.MQTT_HOST}:8883`, {
  clientId: process.env.MQTT_CLIENT_ID,   // YOUR_CLIENT_ID del portal
  username: process.env.MQTT_USERNAME,    // YOUR_USERNAME
  password: process.env.MQTT_PASSWORD,    // YOUR_PASSWORD
  protocolVersion: 5,
  clean: false,
  properties: { sessionExpiryInterval: 3600 },
});

client.on("connect", () => console.log("Connected to private broker"));
client.on("error", (err) => console.error(err.message));

ConnectMQ usa un certificado de Let's Encrypt, en el que Node.js confía de serie, así que no necesitas pasar un archivo de CA. Nunca uses rejectUnauthorized: false en producción; desactiva la comprobación de certificados y deja TLS sin sentido.

Resolución de problemas de MQTT.js en Node.js

  1. Se conecta y se desconecta cada pocos segundos. Otro cliente usa el mismo client ID. Cada proceso en ejecución necesita su propio ID.
  2. ERR_REQUIRE_ESM o Cannot use import statement. Establece "type": "module" en package.json o usa const mqtt = require("mqtt") en archivos CommonJS.
  3. No llegan mensajes tras un corte de red. Te suscribiste solo una vez fuera del handler connect usando clean: true.
  4. connack timeout. Un firewall bloquea el puerto 8883. Prueba con mosquitto_sub -h broker.connectmq.com -p 8883 --capath /etc/ssl/certs -i test-1 -t test o usa wss:// en el 8084.
  5. Suscripción concedida con QoS 128 o un reason code de 0x80 o superior. El broker rechazó la suscripción, a menudo por un filtro no válido o por el límite de suscripciones del broker público (10 por cliente).
  6. La memoria crece mientras está offline. Los mensajes salientes se encolan hasta la reconexión. Limita tu propio búfer o usa QoS 0 con queueQoSZero: false para telemetría desechable.

Próximos pasos

A partir de aquí puedes llevar el mismo cliente a un frontend: React con MQTT construye un dashboard basado en hooks sobre la versión para navegador de MQTT.js, y MQTT con tipos seguros en TypeScript añade topics tipados y payloads validados sobre este código. Para otros entornos de ejecución, la guía de librerías cliente MQTT compara las principales opciones. La lista completa de opciones está en el README de MQTT.js.

Empieza con ConnectMQ

Lleva tus servicios Node.js del broker público de pruebas a uno privado en minutos. Regístrate en app.connectmq.com, elige el plan Serverless gratuito sin tarjeta de crédito, crea un broker y coloca las credenciales generadas en tu entorno. Con un broker MQTT gestionado, ConnectMQ opera los endpoints TLS y la infraestructura del broker mientras tú te centras en el código de tu aplicación.

Preguntas frecuentes

¿Es MQTT.js v5 compatible con código antiguo?

En gran medida. La API de callbacks sigue funcionando, y v5 añade métodos basados en promesas como connectAsync, publishAsync, subscribeAsync y endAsync. Los cambios principales son la reescritura en TypeScript y una versión mínima de Node.js más reciente.

¿Debo usar MQTT 3.1.1 o MQTT 5 con MQTT.js?

Usa protocolVersion: 5 cuando tu broker lo admita, como hace ConnectMQ. Obtienes reason codes, expiración de mensajes, expiración de sesión y propiedades de usuario, mientras que el código de publicación y suscripción sigue siendo el mismo.

¿Cuántas conexiones debe abrir un servicio Node.js?

Normalmente una. Un único cliente MQTT.js puede publicar y suscribirse a muchos topics a la vez, y cada conexión adicional necesita su propio client ID único y cuenta para el límite de conexiones de tu plan.

¿Puedo usar MQTT.js con Bun o Deno?

MQTT.js está pensado para Node.js y navegadores. Los entornos con buena compatibilidad con Node.js a menudo pueden ejecutarlo, pero prueba con cuidado el comportamiento de TLS y de las reconexiones antes de depender de ello.

Artículos relacionados

Las mejores herramientas CLI de MQTT para pruebas, scripts y depuración

Una comparativa práctica de mosquitto_pub/sub, MQTTX CLI, HiveMQ MQTT CLI, la CLI de MQTT.js, curl y mqttui, con la misma prueba ejecutada contra broker.connectmq.com para que elijas la herramienta adecuada para scripts y depuración.

9 min de lectura

Todos los artículos →

Tu broker MQTT privado, a unos pocos clics

Crea una cuenta, obtén las credenciales de tu broker y conecta tu primer dispositivo, sin servidores que instalar, parchear ni escalar.