MQTT erledigt den schwierigsten Teil einer Chat-App bereits: Es schiebt jede Nachricht in Echtzeit an alle Abonnenten. Eine Chat-App auf MQTT läuft auf drei Entscheidungen hinaus: wie Räume auf Topics abgebildet werden, wie der Online-Status funktioniert und wie du verhinderst, dass jemand unter fremdem Namen schreibt. Dieses Tutorial baut einen funktionierenden Chat mit mehreren Räumen in einer einzigen HTML-Datei mit MQTT.js, verbunden über sichere WebSockets, und zeigt anschließend, wie du ihn testest und absicherst.
Topic-Design
| Topic | Payload | QoS | Retained | Zweck |
|---|---|---|---|---|
testmqtt/chat/<room>/messages/<user> | {"text":"…","ts":…} | 1 | Nein | Chatnachrichten |
testmqtt/chat/<room>/presence/<user> | {"status":"online"} oder {"status":"offline"} | 1 | Ja | Wer im Raum ist |
Zwei Entscheidungen in diesem Layout erledigen den Großteil der Arbeit:
- Der Absender ist Teil des Topics. Ein Client abonniert
testmqtt/chat/<room>/messages/+und liest den Benutzernamen aus der letzten Topic-Ebene. Mit einer ACL, die jedem Nutzer nur das Schreiben unter dem eigenen Namen erlaubt (siehe unten), lässt sich der Absender nicht mehr fälschen. Ein"user"-Feld im JSON ließe sich fälschen. - Presence ist eine Retained Message pro Nutzer. Retained Messages werden direkt beim Abonnieren zugestellt, also sieht ein Neuankömmling sofort die komplette Mitgliederliste, ohne jemanden fragen zu müssen.
Räume sind einfach verschiedene Werte von <room>. Du bekommst also beliebig viele Räume ohne jede Broker-Konfiguration. Die Best Practices für Topic-Design erklären, warum stabile Hierarchien mit dem Bezeichner am Ende, wie diese hier, gut skalieren.
Presence mit Retained Messages und Last Will
Presence kombiniert zwei MQTT-Features:
- Nach dem Verbinden publisht der Client
{"status":"online"}retained auf sein eigenes Presence-Topic. - Im CONNECT-Paket registriert derselbe Client einen Last Will:
{"status":"offline"}, ebenfalls retained, auf demselben Topic.
Endet die Verbindung ohne sauberes MQTT-DISCONNECT (Tab geschlossen, Laptop im Ruhezustand, Netz weg), publisht der Broker den Will. Weil er retained ist, ersetzt er die „online“-Nachricht, und alle Mitglieder sehen, dass der Nutzer offline ist. Klickt der Nutzer auf Leave, löscht der Client seine Presence bewusst, indem er einen leeren retained Payload publisht. Das entfernt die Retained Message.
Die komplette Chat-Seite
Speichere das als chat.html und öffne es zweimal: chat.html?user=alice und chat.html?user=bob. Hänge &room=deinteam an, um einen Raum zu wählen. Auf dem gemeinsamen Test-Endpoint solltest du einen ungewöhnlichen Raumnamen nehmen, damit du nicht mit Fremden chattest.
@@CHAT@@
Ein paar Details, die erwähnenswert sind:
textContent, niemalsinnerHTML. Chatnachrichten sind nicht vertrauenswürdige Eingaben anderer Nutzer. Sie als HTML zu rendern ist eine Cross-Site-Scripting-Lücke.- Raum- und Benutzernamen werden bereinigt, damit ein Name mit
/,+oder#nicht aus seiner Topic-Ebene ausbrechen kann. - Deine eigenen Nachrichten kommen über den Broker zurück. Das ist deine Zustellbestätigung. Willst du sie lieber lokal rendern, kannst du in MQTT 5 mit
{ qos: 1, nl: true }(No Local) abonnieren, dann schickt der Broker sie nicht zurück. - Fehlerhafte Payloads werden ignoriert, statt den Handler abstürzen zu lassen, denn jeder mit Publish-Rechten kann beliebige Daten senden.
Wenn das MQTT.js-Setup für dich neu ist, fang mit MQTT im Browser mit reinem JavaScript an. Dort werden die Verbindungsoptionen und der Debugging-Ablauf erklärt.
Testen
Öffne zwei Tabs (alice und bob) und geh diese Checks durch:
- Nachrichten fließen in beide Richtungen. Jede Nachricht erscheint in beiden Tabs, der Absender stammt aus dem Topic.
- Nachzügler sehen die Presence. Öffne einen dritten Tab: Die Mitgliederliste füllt sich sofort aus den Retained Messages.
- Der Will feuert. Schließ bobs Tab. Alice sollte bob innerhalb von Sekunden ausgegraut sehen.
- Leave räumt auf. Klick in einem Tab auf Leave: Der Nutzer verschwindet aus der Liste, statt als offline angezeigt zu werden.
Um den rohen Traffic zu sehen, abonniere testmqtt/chat/lobby/# im Online-MQTT-Client oder im Terminal:
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}"Der zweite Befehl zeigt, dass jeder Dienst (CI, Monitoring, ein Backend) mit einem einzigen Publish in einen Raum posten kann. Weitere Möglichkeiten, den Will zu prüfen, findest du unter Last Will testen.
Die Lobby im Online-Client beobachten
Identität per ACL durchsetzen
Auf einem offenen Test-Endpoint kann jeder auf testmqtt/chat/lobby/messages/alice publishen. In Produktion bekommt jeder Nutzer eigene Broker-Zugangsdaten und eine ACL, die Topics an den Benutzernamen bindet. In Mosquitto ersetzen pattern-Regeln %u durch den authentifizierten Benutzernamen:
# 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/%uDer Broker prüft den Will gegen dieselben Regeln, ein Client kann also auch keinen Will auf das Presence-Topic eines anderen registrieren. Prüf danach mit ACL-Berechtigungstests, ob ein Publish auf das Topic eines anderen Nutzers wirklich abgewiesen wird. Bei MQTT 3.1.1 wird so ein Publish meist stillschweigend verworfen. Prüf also, dass er nie ankommt, statt auf eine Fehlermeldung zu warten.
Was MQTT nicht mitbringt
| Anforderung | Nur MQTT | Übliche Lösung |
|---|---|---|
| Nachrichtenverlauf zum Zurückscrollen | Nein (nur die letzte Retained Message pro Topic) | Backend-Subscriber, der in eine Datenbank schreibt, plus HTTP-Endpoint für den Verlauf |
| Zustellung an Nutzer, die stundenlang weg waren | Persistente Sessions puffern Nachrichten, aber nur innerhalb der Broker-Limits | Datenbank plus Abruf „seit zuletzt gesehen“ beim Reconnect |
| Lesebestätigungen und Tipp-Anzeige | Nicht eingebaut | Zusätzliche Topics, z. B. .../typing/<user> mit QoS 0 |
| Moderation, Löschen von Nachrichten | Nein | Backend-Dienst mit Publish-Rechten auf ein System-Topic |
Persistente Sessions brauchen außerdem einen Broker, der den Session-Zustand behält. Der gemeinsame Test-Endpoint tut das nicht, also teste das Aufholen von Nachrichten auf deiner eigenen Instanz.
Ein Echtzeit-Chat auf MQTT ist vor allem Topic-Design: Räume als Topic-Ebenen, der Absender im Topic, retained Presence mit Last Will und ACLs, die jeden Nutzer in seiner Spur halten. Bau ihn in einer HTML-Datei, prüf jedes Verhalten mit zwei Tabs und einem Subscriber auf #, und nimm eine Datenbank erst dazu, wenn Nutzer einen Verlauf brauchen.
Häufig gestellte Fragen
Ist MQTT ein gutes Protokoll für Chat?
Für die Live-Zustellung ja: Publish/Subscribe passt natürlich zu Räumen, Nachrichten sind klein, und Last Will liefert den Online-Status gratis mit. MQTT speichert aber keinen Chatverlauf. Kombiniere es mit einer Datenbank, wenn Nutzer zurückscrollen oder nach einer Offline-Phase aufholen sollen.
Wie zeige ich mit MQTT an, wer online ist?
Jeder Client publisht eine retained 'online'-Nachricht auf sein eigenes Presence-Topic und registriert auf demselben Topic einen retained 'offline'-Last-Will. Neue Clients bekommen beim Abonnieren alle retained Presence-Nachrichten, und der Broker publisht den Will, wenn ein Client verschwindet.
Wie verhindere ich, dass sich Nutzer als andere ausgeben?
Schreib den Absendernamen ins Topic, etwa chat/<raum>/messages/<user>, und nutze eine Broker-ACL, die jedem Nutzer nur das Publishen unter dem eigenen Namen erlaubt. Empfänger vertrauen dann dem Topic, nicht einem Namensfeld im Payload.


