Tutoriales de IoT

MQTT sobre WebSocket: conecta un navegador a un broker MQTT con MQTT.js

Los navegadores no pueden abrir sockets TCP directos, así que las aplicaciones web hablan MQTT sobre WebSocket. Construye un cliente de navegador y un cliente Node.js funcionales contra broker.connectmq.com y aprende a resolver los errores de conexión más comunes.

Panel en el navegador conectándose a un broker MQTT mediante un WebSocket seguro (MQTT sobre WebSocket)
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 sobre WebSocket permite que una página web publique y se suscriba a un broker MQTT directamente desde el navegador, sin un backend que haga de intermediario. En este tutorial aprenderás por qué los navegadores necesitan el transporte WebSocket, la diferencia entre ws y wss, y cómo construir una página HTML completa y un cliente Node.js que se comuniquen con el servidor MQTT público gratuito broker.connectmq.com, pensado para pruebas y no para datos de producción.

¿Los brokers son nuevos para ti? Lee primero ¿Qué es un broker MQTT? para conocer los fundamentos de publicación/suscripción.

Por qué los navegadores necesitan MQTT sobre WebSocket

MQTT nativo funciona sobre una conexión TCP sin más, normalmente en el puerto 1883 (o el 8883 con TLS). Las aplicaciones de escritorio, los servidores y los microcontroladores pueden abrir sockets TCP libremente. Los navegadores no: por motivos de seguridad, el JavaScript de una página web solo tiene acceso a HTTP, WebSocket y algunos otros transportes gestionados por el navegador.

WebSocket resuelve el problema. Empieza como una petición HTTP normal con la cabecera Upgrade: websocket y, cuando el servidor responde 101 Switching Protocols, la conexión se convierte en un canal binario full-duplex. A partir de ahí, los paquetes MQTT (CONNECT, PUBLISH, SUBSCRIBE, etc.) viajan sin cambios dentro de frames WebSocket. Durante el handshake, el cliente solicita el subprotocolo mqtt para que el broker sepa qué esperar.

Browser (MQTT.js)                                broker.connectmq.com:8084
    |-- TLS handshake ------------------------------>|
    |-- GET /mqtt  Upgrade: websocket                 |
    |   Sec-WebSocket-Protocol: mqtt ---------------->|
    |<-- 101 Switching Protocols ---------------------|
    |== WebSocket frame [ MQTT CONNECT ] ============>|
    |<= WebSocket frame [ MQTT CONNACK ] =============|
    |== WebSocket frame [ MQTT SUBSCRIBE ] ==========>|
    |<= WebSocket frame [ MQTT PUBLISH ] =============|

Desde el punto de vista del broker, un cliente WebSocket es un cliente MQTT más. Puede comunicarse con dispositivos conectados por TCP, compartir topics con ellos y usar los mismos niveles de QoS, retained messages y funciones de MQTT 5.

Usos típicos de MQTT sobre WebSocket:

  • Paneles IoT en tiempo real que se actualizan en cuanto un sensor publica
  • Paneles de control en el navegador que envían comandos a los dispositivos
  • Herramientas de monitorización y gemelos digitales 3D que visualizan el estado de las máquinas en tiempo real
  • Aplicaciones móviles híbridas construidas con tecnologías web

ws frente a wss: puertos y rutas en ConnectMQ

Igual que HTTP y HTTPS, WebSocket tiene una forma sin cifrar (ws://) y otra cifrada con TLS (wss://).

Transporte Esquema Puerto URL completa Cuándo usarlo
MQTT (TCP) mqtt:// 1883 mqtt://broker.connectmq.com:1883 Dispositivos, servidores, CLI
MQTT sobre TLS mqtts:// 8883 mqtts://broker.connectmq.com:8883 Dispositivos y servidores en producción
WebSocket ws:// 8083 ws://broker.connectmq.com:8083/mqtt Pruebas locales desde una página http://
WebSocket seguro wss:// 8084 wss://broker.connectmq.com:8084/mqtt Navegadores y aplicaciones web en producción

Hay dos detalles importantes:

  • La ruta es /mqtt. Una URL WebSocket es una URL HTTP, así que tiene ruta. Conectarse a wss://broker.connectmq.com:8084 sin /mqtt hace fallar el handshake.
  • Prefiere wss. Las páginas servidas por HTTPS solo pueden abrir conexiones wss://, y las credenciales enviadas por ws:// sin cifrar viajan en texto plano. ConnectMQ sirve WSS en el 8084 con un certificado de Let's Encrypt, así que los navegadores confían en él sin configuración adicional.

Construye una página MQTT sobre WebSocket con MQTT.js

La siguiente página está completa y es autónoma. Guárdala como index.html y ábrela en un navegador. Carga MQTT.js desde una CDN, se conecta de forma anónima al área pública de broker.connectmq.com, se suscribe a un topic único y te permite publicar mensajes.

<!DOCTYPE html>
<html lang="en">
<head>
  <meta charset="utf-8">
  <title>MQTT over WebSocket demo</title>
  <script src="https://unpkg.com/mqtt@5.10.3/dist/mqtt.min.js"></script>
</head>
<body>
  <p>Status: <span id="status">connecting...</span></p>
  <input id="msg" value="Hello from the browser">
  <button id="send">Publish</button>
  <pre id="log"></pre>

  <script>
    const id = Math.random().toString(16).slice(2, 10);
    const clientId = "web-" + id;
    const topic = "connectmq/demo/" + id + "/chat";
    const log = (line) => {
      document.getElementById("log").textContent += line + "\n";
    };

    const client = mqtt.connect("wss://broker.connectmq.com:8084/mqtt", {
      clientId: clientId,
      protocolVersion: 5,
      clean: true,
      keepalive: 30,
      reconnectPeriod: 2000,
      connectTimeout: 10000
    });

    client.on("connect", () => {
      document.getElementById("status").textContent = "connected as " + clientId;
      client.subscribe(topic, { qos: 1 }, (err, granted) => {
        if (err) return log("Subscribe error: " + err.message);
        log("Subscribed to " + granted[0].topic + " (QoS " + granted[0].qos + ")");
      });
    });

    client.on("message", (t, payload) => {
      log("[" + t + "] " + payload.toString());
    });

    client.on("reconnect", () => {
      document.getElementById("status").textContent = "reconnecting...";
    });

    client.on("error", (err) => log("Error: " + err.message));

    document.getElementById("send").onclick = () => {
      const text = document.getElementById("msg").value;
      client.publish(topic, text, { qos: 1 });
    };
  </script>
</body>
</html>

Al hacer clic en Publish, el mensaje viaja al broker por el WebSocket seguro y vuelve directamente a través de tu propia suscripción. Abre la página en dos pestañas y verás que cada una obtiene su propio topic aleatorio, así que no se ven entre sí. Para compartir un topic, escribe a mano el mismo topic connectmq/demo/<your-id>/chat en ambas.

Algunas notas sobre las opciones:

  • clientId debe ser no vacío y único. El área pública rechaza un client ID vacío, y dos clientes con el mismo ID se expulsan mutuamente una y otra vez.
  • protocolVersion: 5 usa MQTT 5. Usa 4 para MQTT 3.1.1 si lo necesitas.
  • keepalive: 30 hace que el cliente envíe un PINGREQ cuando la conexión está inactiva, para que el broker y los proxies sepan que sigue viva.
  • reconnectPeriod: 2000 hace que MQTT.js se reconecte automáticamente dos segundos después de una caída.

También puedes ver los mensajes del navegador desde una terminal con la CLI de Mosquitto, porque los clientes TCP y WebSocket comparten los mismos topics. Sustituye abc12345 por el id que aparece en el topic de la página:

mosquitto_sub -h broker.connectmq.com -p 1883 -i cli-watch-abc12345 -t "connectmq/demo/abc12345/#" -v

MQTT sobre WebSocket en Node.js

Node.js puede usar TCP directamente, pero WebSocket resulta útil cuando tu servidor está detrás de un proxy o un firewall que solo permite tráfico de tipo HTTPS, o cuando quieres compartir código con una aplicación de navegador. Instala MQTT.js con npm install mqtt y guarda esto como ws-client.mjs:

import mqtt from "mqtt";

const id = Math.random().toString(16).slice(2, 10);
const clientId = `node-${id}`;
const topic = `connectmq/demo/${id}/hello`;

const client = mqtt.connect("wss://broker.connectmq.com:8084/mqtt", {
  clientId,
  protocolVersion: 5,
  keepalive: 30,
});

client.on("connect", (connack) => {
  console.log("Connected, CONNACK properties:", connack.properties);
  client.subscribe(topic, { qos: 1 }, (err, granted) => {
    if (err) throw err;
    console.log("Granted:", granted);
    client.publish(topic, JSON.stringify({ temp: 21.5, ts: Date.now() }), { qos: 1 });
  });
});

client.on("message", (t, payload) => {
  console.log(`Received on ${t}: ${payload.toString()}`);
  client.end();
});

client.on("error", (err) => {
  console.error("Connection error:", err.message);
  client.end();
});

Ejecútalo con node ws-client.mjs. La salida será parecida a esta:

Connected, CONNACK properties: { receiveMaximum: 16, topicAliasMaximum: 65535 }
Granted: [ { topic: 'connectmq/demo/4df8521c/hello', qos: 1, nl: false, rap: false, rh: 0 } ]
Received on connectmq/demo/4df8521c/hello: {"temp":21.5,"ts":1790000000000}

La única diferencia con un cliente TCP es la URL. Cámbiala por mqtt://broker.connectmq.com:1883 y el resto del código sigue igual. Si quieres entender los valores de qos anteriores, consulta Niveles de QoS en MQTT explicados.

Solución de problemas en conexiones MQTT sobre WebSocket

La mayoría de los problemas se manifiestan como una conexión que nunca llega a connect o que se reconecta sin parar. Abre las herramientas de desarrollo del navegador, revisa la consola y la pestaña Red (filtra por WS) y compara con esta tabla.

Síntoma Causa probable Solución
Error «Mixed Content», conexión bloqueada Una página https:// intenta abrir ws:// Usa wss://broker.connectmq.com:8084/mqtt
Falla el handshake, HTTP 404 en la pestaña Red Ruta ausente o incorrecta Añade /mqtt a la URL
Falla el handshake en el 8083 o el 8084 Esquema y puerto cruzados (ws en el 8084, wss en el 8083) ws va con el 8083 y wss con el 8084
Sospechas de un problema de CORS WebSocket no se rige por CORS como fetch/XHR Busca la causa real: URL, puerto, TLS o credenciales
Se conecta y se desconecta cada pocos segundos Dos clientes usan el mismo client ID Genera un ID único por pestaña o dispositivo
Conexión rechazada justo después del CONNECT Client ID vacío o usuario/contraseña incorrectos Usa un client ID no vacío; revisa las credenciales del portal
Se cae tras un periodo de inactividad Keepalive demasiado largo para un proxy o una red móvil Usa keepalive: 30 o menos
Funciona en local, falla en una red corporativa Un proxy o firewall bloquea puertos no estándar Prueba desde otra red o pide a TI que permita el tráfico saliente al puerto 8084

Dos de ellos merecen una mirada más detallada.

Contenido mixto

Los navegadores bloquean los subrecursos inseguros en páginas seguras. Si tu panel se sirve por https://, cualquier conexión ws:// se rechaza antes de salir del navegador y el broker ni siquiera la ve. La solución es siempre wss://. El endpoint sin cifrar ws://broker.connectmq.com:8083/mqtt solo sirve para páginas abiertas desde http://localhost o desde un archivo local.

Conflictos de client ID

MQTT solo permite una conexión por client ID. Cuando llega una segunda conexión con el mismo ID, el broker cierra la primera. En un navegador esto suele ocurrir cuando un usuario abre la misma página en dos pestañas y el client ID está escrito a mano en el código. Con la reconexión automática activada, ambas pestañas se pelean por el ID indefinidamente. Añade un sufijo aleatorio por pestaña. En un broker privado de ConnectMQ, los client IDs están restringidos al client ID emitido y a sus variantes _suffix, así que usa YOUR_CLIENT_ID_tab1, YOUR_CLIENT_ID_tab2, etc.

Producción: usar las credenciales del portal

El área pública es para pruebas: todo lo que se publica allí puede leerlo cualquiera y tiene límites estrictos (64 KB por mensaje, 10 mensajes por segundo por cliente). Para una aplicación real, crea un broker privado en el portal y conéctate con las credenciales generadas:

const client = mqtt.connect("wss://broker.connectmq.com:8084/mqtt", {
  clientId: "YOUR_CLIENT_ID_dashboard",
  username: "YOUR_USERNAME",
  password: "YOUR_PASSWORD",
  protocolVersion: 5,
  keepalive: 30,
});

Ten en cuenta estos puntos en las aplicaciones de navegador:

  • Usa siempre wss en producción. El usuario y la contraseña se envían en el paquete CONNECT, así que el transporte debe ir cifrado.
  • Considera visibles las credenciales del navegador. Todo lo que se entrega a un navegador puede leerlo su usuario. Da a la aplicación web sus propias credenciales en lugar de reutilizar las de los dispositivos y evita incrustarlas en páginas públicas.
  • Respeta los límites del plan. Cada pestaña abierta es una conexión. El plan Serverless (Free) permite hasta 25 conexiones simultáneas, suficiente para prototipos. Los paneles de producción encajan mejor en Serverless (Paid), con hasta 1.000 conexiones, o en un clúster Dedicated.
  • Gestiona bien las reconexiones. Muestra un indicador de estado y vuelve a suscribirte, o apóyate en una sesión persistente, cuando regrese la conexión.

Encontrarás más consejos de refuerzo en Buenas prácticas de seguridad en MQTT.

Empieza con ConnectMQ

Puedes probar MQTT sobre WebSocket ahora mismo con el endpoint público wss://broker.connectmq.com:8084/mqtt. Cuando estés listo para construir un panel real sobre un broker MQTT gestionado, regístrate gratis en app.connectmq.com (plan Serverless gratuito, sin tarjeta de crédito), crea un broker y conéctate con tu propio usuario, contraseña y client ID. El cliente MQTT integrado en el navegador y el explorador de topics en tiempo real del portal facilitan comprobar que tu aplicación web publica lo que esperas.

Preguntas frecuentes

¿Qué puerto usa MQTT sobre WebSocket?

No hay un único puerto estándar. En broker.connectmq.com, WebSocket sin cifrar usa el puerto 8083 y WebSocket seguro el 8084, ambos con la ruta /mqtt.

¿Es MQTT sobre WebSocket más lento que MQTT sobre TCP?

La sobrecarga es pequeña. Tras el upgrade HTTP inicial, cada paquete MQTT va envuelto en un frame WebSocket que solo añade unos pocos bytes, así que la latencia es cercana a la de TCP directo en la mayoría de las aplicaciones.

¿Necesito un servidor backend para usar MQTT en el navegador?

No. Con MQTT sobre WebSocket, el navegador se conecta directamente al broker. Solo necesitas un backend si quieres mantener las credenciales fuera del cliente o añadir tu propia lógica de negocio.

¿Se aplica CORS a MQTT sobre WebSocket?

No: las conexiones WebSocket no están sujetas a las comprobaciones preflight de CORS como las peticiones fetch y XHR. Si una conexión falla, revisa la URL, el puerto, la ruta y las credenciales.

¿Pueden los clientes de navegador y los dispositivos ESP32 compartir los mismos topics?

Sí. El broker enruta los mensajes por topic independientemente del transporte, así que un navegador en WSS y un dispositivo en el puerto TCP 1883 pueden publicar y suscribirse a los mismos topics. Consulta el tutorial de ESP32 con MQTT para la parte del dispositivo.

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.