Tutoriales

Crea una app de chat en tiempo real con MQTT

Crea un chat en el navegador sobre MQTT: salas como topics, presencia con mensajes retenidos y Last Will, identidad del remitente con ACL, todo en un solo HTML.

Actualizado · 9 min de lectura

Crea una app de chat en tiempo real con MQTT

MQTT ya resuelve la parte difícil de una app de chat: envía cada mensaje a todos los suscriptores en tiempo real. Un chat sobre MQTT se reduce a tres decisiones: cómo se traducen las salas a topics, cómo funciona la presencia y cómo impides que alguien publique con el nombre de otra persona. Este tutorial construye un chat multisala funcional en un único archivo HTML con MQTT.js, conectado por WebSockets seguros, y después muestra cómo probarlo y reforzarlo.

Diseño de topics

TopicPayloadQoSRetenidoPropósito
testmqtt/chat/<room>/messages/<user>{"text":"…","ts":…}1NoMensajes del chat
testmqtt/chat/<room>/presence/<user>{"status":"online"} o {"status":"offline"}1SíQuién está en la sala

Dos decisiones de este esquema hacen casi todo el trabajo:

  • El remitente forma parte del topic. Un cliente se suscribe a testmqtt/chat/<room>/messages/+ y lee el nombre de usuario del último nivel del topic. Con una ACL que solo permite a cada usuario escribir bajo su propio nombre (lo vemos más abajo), el remitente ya no se puede falsificar. Un campo "user" dentro del JSON sí podría falsificarse.
  • La presencia es un mensaje retenido por usuario. Los mensajes retenidos se entregan justo cuando un cliente se suscribe, así que quien llega ve la lista completa de miembros al instante, sin preguntar a nadie.

Las salas son simplemente valores distintos de <room>, así que tienes tantas salas como quieras sin configurar nada en el broker. Las buenas prácticas de diseño de topics explican por qué jerarquías estables con el identificador al final, como esta, escalan bien.

Presencia con mensajes retenidos y Last Will

La presencia combina dos funcionalidades de MQTT:

  1. Tras conectar, el cliente publica {"status":"online"} retenido en su propio topic de presencia.
  2. En el paquete CONNECT, ese mismo cliente registra un Last Will: {"status":"offline"}, también retenido, en el mismo topic.

Si la conexión termina sin un DISCONNECT MQTT limpio (pestaña cerrada, portátil suspendido, red caída), el broker publica el will. Como es retenido, sustituye al mensaje "online" y todos los miembros ven que el usuario se ha desconectado. Cuando el usuario pulsa Leave, el cliente borra su presencia a propósito publicando un payload retenido vacío, lo que elimina el mensaje retenido.

La página de chat completa

Guarda esto como chat.html y ábrelo dos veces: chat.html?user=alice y chat.html?user=bob. Añade &room=tuequipo para elegir una sala. En el endpoint de pruebas compartido, elige un nombre de sala poco común para no chatear con desconocidos.

@@CHAT@@

Algunos detalles que conviene destacar:

  • textContent, nunca innerHTML. Los mensajes del chat son entradas no confiables de otros usuarios. Renderizarlos como HTML abre un agujero de cross-site scripting.
  • Los nombres de sala y de usuario se sanean para que un nombre con /, + o # no pueda salirse de su nivel de topic.
  • Tus propios mensajes vuelven a través del broker. Esa es tu confirmación de entrega. Si prefieres pintarlos localmente, MQTT 5 te permite suscribirte con { qos: 1, nl: true } (No Local) para que el broker no te los devuelva.
  • Los payloads mal formados se ignoran en lugar de romper el manejador, porque cualquiera con permiso de publicación puede enviar cualquier cosa.

Si la configuración de MQTT.js es nueva para ti, empieza por MQTT en el navegador con JavaScript puro, que explica las opciones de conexión y el flujo de depuración.

Pruébalo

Abre dos pestañas (alice y bob) y repasa estas comprobaciones:

  1. Los mensajes fluyen en ambos sentidos. Cada mensaje aparece en las dos pestañas, con el remitente tomado del topic.
  2. Quien llega tarde ve la presencia. Abre una tercera pestaña: la lista de miembros se rellena al instante con los mensajes retenidos.
  3. El will se dispara. Cierra la pestaña de bob. Alice debería ver a bob en gris en pocos segundos.
  4. Leave limpia. Pulsa Leave en una pestaña: el usuario desaparece de la lista en lugar de aparecer como offline.

Para ver el tráfico en bruto, suscríbete a testmqtt/chat/lobby/# en el cliente MQTT online, o desde una terminal:

Observar una sala y publicar como bot
mosquitto_sub -h broker.connectmq.com -t 'testmqtt/chat/lobby/#' -v

mosquitto_pub -h broker.connectmq.com -q 1 \
  -t testmqtt/chat/lobby/messages/deploy-bot \
  -m "{\"text\":\"Build 412 deployed\",\"ts\":$(date +%s)000}"

El segundo comando demuestra que cualquier servicio (CI, monitorización, un backend) puede escribir en una sala con un solo publish. Para más formas de poner a prueba el will, consulta probar Last Will.

Observa el lobby en el cliente online

Garantiza la identidad con ACL

En un endpoint de pruebas abierto, cualquiera puede publicar en testmqtt/chat/lobby/messages/alice. En producción, da a cada usuario sus propias credenciales del broker y una ACL que vincule los topics a su nombre de usuario. En Mosquitto, las reglas pattern sustituyen %u por el nombre de usuario autenticado:

acl_file (Mosquitto)
# Everyone can read every room
pattern read testmqtt/chat/+/#

# ...but may only write under their own name
pattern write testmqtt/chat/+/messages/%u
pattern write testmqtt/chat/+/presence/%u

El broker comprueba el will con las mismas reglas, así que un cliente tampoco puede registrar un will en el topic de presencia de otro. Después usa las pruebas de permisos ACL para verificar que un publish en el topic de otro usuario se rechaza de verdad. Con MQTT 3.1.1 ese publish suele descartarse en silencio, así que comprueba que nunca llega en lugar de esperar un error.

Lo que MQTT no te da

NecesidadSolo MQTTSolución habitual
Historial de mensajesNo (solo el último mensaje retenido por topic)Un suscriptor en el backend que escribe en una base de datos, más un endpoint HTTP de historial
Entrega a un usuario que estuvo fuera durante horasLas sesiones persistentes encolan mensajes, pero dentro de los límites del brokerBase de datos más una consulta "desde la última conexión" al reconectar
Confirmaciones de lectura e indicador de escrituraNo incluidoTopics adicionales, p. ej. .../typing/<user> con QoS 0
Moderación, borrado de mensajesNoServicio backend con permiso de publicación en un topic de sistema

Las sesiones persistentes también necesitan un broker que conserve el estado de la sesión. El endpoint de pruebas compartido no lo hace, así que prueba la recuperación de mensajes en tu propia instancia.

Un chat en tiempo real sobre MQTT es, sobre todo, diseño de topics: salas como niveles de topic, el remitente en el topic, presencia retenida con Last Will y ACL que mantienen a cada usuario en su carril. Constrúyelo en un archivo HTML, comprueba cada comportamiento con dos pestañas y un suscriptor observando #, y añade una base de datos solo cuando los usuarios necesiten historial.

Preguntas frecuentes

¿Es MQTT un buen protocolo para un chat?

Para la entrega en vivo, sí: publish/subscribe encaja de forma natural con las salas, los mensajes son pequeños y Last Will te da la presencia gratis. Pero MQTT no guarda el historial del chat, así que combínalo con una base de datos si los usuarios necesitan desplazarse hacia atrás o ponerse al día tras estar desconectados.

¿Cómo muestro quién está conectado con MQTT?

Cada cliente publica un mensaje retenido 'online' en su propio topic de presencia y registra un Last Will retenido 'offline' en ese mismo topic. Los clientes nuevos reciben todos los mensajes de presencia retenidos al suscribirse, y el broker publica el will cuando un cliente desaparece.

¿Cómo evito que unos usuarios se hagan pasar por otros?

Pon el nombre del remitente en el topic, por ejemplo chat/<sala>/messages/<usuario>, y usa una ACL del broker que solo permita a cada usuario publicar bajo su propio nombre. Así los receptores confían en el topic, no en un campo de nombre dentro del payload.