Meist wird MQTT verwendet, um Sensordaten oder Schaltzustände zu übertragen: Temperaturwerte, Luftfeuchtigkeit oder das Ein- und Ausschalten eines Relais. Klassisches IoT eben.
Doch was viele dabei übersehen:
MQTT ist völlig unabhängig davon, was du überträgst.
Ob Zahlen, Texte oder sogar strukturierte Daten – für MQTT ist alles einfach nur eine Nachricht.
Und genau hier wird es spannend.
Denn im Grunde passiert beim Chatten nichts anderes:
Eine Nachricht wird gesendet, verteilt und von anderen empfangen.
👉 Genau das macht MQTT bereits – nur normalerweise ohne Menschen.
Das bedeutet:
👉 Geräte „chatten“ längst miteinander – wir müssen nur den Menschen mit ins Spiel bringen.
In diesem Beitrag zeige ich dir, wie du mit MQTT, Node-RED und einer kleinen SQLite-Datenbank einen eigenen Chat baust.
Kein riesiges Framework, keine komplizierte Architektur – sondern ein leicht verständliches Praxisprojekt, das dir gleichzeitig zeigt, wie flexibel MQTT wirklich ist.
Und das Beste daran:
Der Chat funktioniert nicht nur im Browser, sondern lässt sich später problemlos auf ESP32, andere Clients oder sogar dein eigenes Smart Home erweitern.
Ziel des Projekts
In diesem Beitrag bauen wir gemeinsam einen einfachen Chat auf Basis von MQTT und Node-RED.
Der Fokus liegt dabei nicht auf einer vollwertigen Chat-Anwendung, sondern auf dem Verständnis der zugrunde liegenden Kommunikation.
Das Ziel ist es, aufzuzeigen, wie Nachrichten über MQTT gesendet, verteilt und von mehreren Teilnehmern empfangen werden können – genau so, wie es auch in klassischen IoT-Szenarien passiert.
Dabei setzen wir auf folgende Komponenten:
- MQTT als Transportprotokoll für die Nachrichten
- Node-RED zur Verarbeitung und Visualisierung
- SQLite zur Speicherung der Chat-Historie
Die Datenbank dient dabei ausschließlich dazu, bereits gesendete Nachrichten persistent zu speichern und beim Beitritt zum Chat erneut anzeigen zu können.
Bewusst verzichten wir in diesem Projekt auf zusätzliche Funktionen wie:
- Upload von Bildern oder Dateien
- Versand von Videos
- komplexe Benutzerverwaltung
Stattdessen konzentrieren wir uns auf das Wesentliche:
👉 Das Senden und Empfangen von Textnachrichten über MQTT
Genau dieser reduzierte Ansatz macht das Projekt so interessant, denn er zeigt sehr deutlich, wie einfach sich mit MQTT ein verteiltes Kommunikationssystem umsetzen lässt.
Architektur & Funktionsweise des Chats
Bevor wir mit der eigentlichen Umsetzung starten, werfen wir einen Blick auf die Architektur unseres Chats und das Zusammenspiel der einzelnen Komponenten.
In diesem Projekt übernimmt Node-RED eine zentrale Rolle:
- Es stellt das Dashboard für die Benutzeroberfläche bereit
- Es fungiert als Publisher und Subscriber im MQTT-System
- Es dient als Schnittstelle zur SQLite-Datenbank, in der die Nachrichten gespeichert werden
Dadurch entsteht eine flexible Architektur, bei der Node-RED zwar das Frontend liefert, aber nicht zwingend der einzige Teilnehmer im System ist.
Offenes System durch MQTT
Ein großer Vorteil von MQTT ist die Offenheit des Systems.
👉 Das bedeutet konkret:
Dein Chat ist nicht auf das Dashboard beschränkt – er ist ein offenes System.
Neben dem Node-RED Dashboard können also auch andere Clients Nachrichten senden und empfangen, zum Beispiel:
- der MQTT Explorer zur direkten Analyse und zum Testen
- ein Mikrocontroller wie ESP8266 oder ESP32
- eigene Anwendungen oder Skripte
Diese Clients können problemlos Nachrichten in den Chat senden oder empfangen.
Einziger Unterschied:
👉 Der Zugriff auf die Chat-Historie erfolgt in unserem Projekt ausschließlich über Node-RED und die angebundene SQLite-Datenbank.
Ablauf einer Chatnachricht
Der Ablauf einer Nachricht ist bewusst einfach gehalten und orientiert sich vollständig am MQTT-Prinzip:
- Der Benutzer meldet sich im Dashboard mit einem Benutzernamen an
- Anschließend kann er – wie in jedem klassischen Chat – eine Nachricht eingeben und absenden
- Node-RED veröffentlicht diese Nachricht auf einem MQTT-Topic
- Der MQTT-Broker verteilt die Nachricht an alle verbundenen Clients
- Node-RED empfängt die Nachricht erneut als Subscriber
- Die Nachricht wird in der SQLite-Datenbank gespeichert
- Der Chatverlauf wird aktualisiert und im Dashboard angezeigt
👉 Wichtig:
Jede Nachricht durchläuft immer denselben Weg – unabhängig davon, ob sie vom Browser, einem Mikrocontroller oder einem anderen MQTT-Client stammt.
Verwendetes MQTT-Topic
Für die Kommunikation verwenden wir ein zentrales Topic:
chat/message
Alle Teilnehmer abonnieren dieses Topic und erhalten dadurch jede neue Nachricht in Echtzeit.
Speicherung der Nachrichten
Da MQTT selbst keine dauerhafte Speicherung vorsieht, wird jede Nachricht zusätzlich in einer SQLite-Datenbank abgelegt.
Das ermöglicht:
- das Nachladen der Chat-Historie beim Einstieg
- die Anzeige vergangener Nachrichten
- einen konsistenten Zustand im Dashboard
Voraussetzungen
Für dieses Projekt benötigst du eine laufende Umgebung mit Node-RED, einem MQTT-Broker sowie einer SQLite-Datenbank.
Die gute Nachricht:
Alle notwendigen Grundlagen habe ich bereits in separaten Beiträgen ausführlich erklärt.
👉 Wenn du diese Komponenten noch nicht eingerichtet hast, findest du hier die passenden Anleitungen:
- Node-RED mit Docker installieren – Schritt für Schritt
- Node-RED unter Windows 11 mit WSL installieren – Schritt für Schritt
- Node-RED mit Docker – SQLite Datenbank anbinden und erste Abfragen
Benötigte Komponenten im Überblick
Für den Chat selbst setzen wir auf folgende Bausteine:
- Node-RED
zur Verarbeitung der Daten und Bereitstellung des Dashboards - MQTT-Broker (z. B. Mosquitto)
für die Verteilung der Nachrichten zwischen den Teilnehmern - SQLite-Datenbank
zur Speicherung der Chat-Historie
Hinweis
In diesem Beitrag konzentrieren wir uns vollständig auf die Umsetzung des Chats.
Die Installation und Grundkonfiguration der einzelnen Komponenten wird daher nicht erneut behandelt.
👉 So kannst du dich direkt auf das Wesentliche konzentrieren:
Das Senden und Empfangen von Nachrichten über MQTT.
Einrichten der SQLite-Datenbank für die Chat-Historie
Die Historie des Chats wird in einer SQLite-Datenbank gespeichert.
Diese liegt als einfache Datei im Dateisystem vor – genau das ist einer der großen Vorteile von SQLite:
Es wird kein separater Datenbankserver benötigt.
Für unser Projekt reicht eine einzige Tabelle aus, da wir zunächst nur einen Chatraum verwenden.
Tabellenstruktur
Die Tabelle chat_messages enthält folgende Spalten:
- id – fortlaufende, eindeutige Nummer (Primärschlüssel)
- name – Name des Benutzers, der die Nachricht gesendet hat
- message – eigentlicher Text der Nachricht
- zeit – UNIX-Timestamp der Nachricht
SQL zum Anlegen der Tabelle
CREATE TABLE chat_messages (
id INTEGER PRIMARY KEY AUTOINCREMENT,
name TEXT NOT NULL,
message TEXT NOT NULL,
zeit INTEGER NOT NULL
);
Node-RED Flow erstellen
Nachdem die Datenbank vorbereitet ist, kümmern wir uns im nächsten Schritt um den eigentlichen Node-RED Flow.
Dieser verbindet das Dashboard, den MQTT-Broker und die SQLite-Datenbank zu einem einfachen, aber erstaunlich flexiblen Chat-System.
👉 Ziel ist es, drei zentrale Komponenten miteinander zu verbinden:
- Benutzeroberfläche (Dashboard)
- Nachrichtenübertragung (MQTT)
- Speicherung der Historie (SQLite)
Aufbau des Flows im Überblick
Der Flow besteht im Kern aus drei Bereichen:
1. Dashboard (ui-template)
Hier wird die Chat-Oberfläche dargestellt und Benutzereingaben werden erfasst.
Code
<template>
<div class="chat-app">
<div v-if="!joined" class="join-box">
<h3>Chat beitreten</h3>
<input
v-model="username"
type="text"
placeholder="Dein Name"
class="input"
@keyup.enter="joinChat"
/>
<button class="btn" @click="joinChat">Absenden</button>
</div>
<div v-else class="chat-wrapper">
<div class="chat-header">
Angemeldet als <b>{{ username }}</b>
</div>
<div class="chat-container" ref="chatContainer">
<div
v-for="(entry, index) in (msg.payload || [])"
:key="index"
class="msg-wrapper"
:class="entry.name === username ? 'me-wrapper' : 'other-wrapper'"
>
<div
class="msg"
:class="entry.name === username ? 'me' : 'other'"
>
<div class="user">{{ entry.name }}</div>
<div class="text">{{ entry.message }}</div>
<div class="time">{{ entry.zeit_format }}</div>
</div>
</div>
</div>
<div class="chat-input-area">
<input
v-model="newMessage"
type="text"
placeholder="Nachricht eingeben..."
class="input message-input"
@keyup.enter="sendMessage"
/>
<button class="btn" @click="sendMessage">Senden</button>
</div>
</div>
</div>
</template>
<script>
export default {
data() {
return {
username: "",
joined: false,
newMessage: ""
}
},
watch: {
msg: {
deep: true,
handler() {
this.$nextTick(() => {
const el = this.$refs.chatContainer
if (el) {
el.scrollTop = el.scrollHeight
}
})
}
}
},
methods: {
joinChat() {
const name = (this.username || "").trim()
if (!name) return
this.username = name
this.joined = true
this.send({
topic: "load_chat",
payload: {
action: "join",
name: this.username
}
})
},
sendMessage() {
const text = (this.newMessage || "").trim()
if (!text || !this.username) return
this.send({
topic: "send_message",
payload: {
name: this.username,
message: text
}
})
this.newMessage = ""
}
}
}
</script>
<style>
.chat-app {
display: flex;
flex-direction: column;
gap: 12px;
}
.join-box {
background: #ffffff;
padding: 16px;
border-radius: 12px;
}
.chat-wrapper {
display: flex;
flex-direction: column;
gap: 10px;
}
.chat-header {
font-size: 0.95rem;
color: #444;
padding: 8px 12px;
background: #ffffff;
border-radius: 10px;
}
.chat-container {
height: 400px;
overflow-y: auto;
padding: 12px;
background: #e5ddd5;
border-radius: 12px;
box-sizing: border-box;
}
.msg-wrapper {
display: flex;
margin: 8px 0;
}
.me-wrapper {
justify-content: flex-end;
}
.other-wrapper {
justify-content: flex-start;
}
.msg {
max-width: 75%;
padding: 8px 12px;
border-radius: 10px;
box-shadow: 0 1px 1px rgba(0,0,0,0.08);
word-break: break-word;
}
.me {
background: #dcf8c6;
}
.other {
background: #ffffff;
}
.user {
font-size: 0.75rem;
font-weight: bold;
color: #555;
margin-bottom: 4px;
}
.text {
font-size: 0.95rem;
color: #222;
}
.time {
font-size: 0.7rem;
color: #888;
margin-top: 6px;
text-align: right;
}
.chat-input-area {
display: flex;
gap: 8px;
}
.input {
flex: 1;
padding: 10px 12px;
border: 1px solid #ccc;
border-radius: 10px;
box-sizing: border-box;
}
.message-input {
min-width: 0;
}
.btn {
padding: 10px 16px;
border: none;
border-radius: 10px;
background: #25d366;
color: white;
cursor: pointer;
}
</style>
Der komplette Flow
👉 Importiere den folgenden Flow in Node-RED:
Verarbeitung eingehender Nachrichten
Sobald eine Nachricht im Dashboard abgesendet wird, passiert Folgendes:
- Das Dashboard sendet eine Nachricht mit:
name(Benutzername)message(Text)
- In der Function-Node „baue Nachricht“ wird die Nachricht erweitert:
let name = msg.payload.name;
let message = msg.payload.message;
let time = Date.now();
msg.payload = {
name: name,
message: message,
timestamp: time
};
return msg;
👉 Hier wird der UNIX-Zeitstempel ergänzt, damit die Nachricht später korrekt sortiert und angezeigt werden kann.
Nachricht via MQTT versenden
Die aufbereitete Nachricht wird anschließend über eine mqtt out-Node veröffentlicht:
Topic:
chat/message
👉 Wichtig: Alle Teilnehmer im System abonnieren genau dieses Topic.
Nachricht empfangen (Subscriber)
Node-RED ist gleichzeitig auch Subscriber und empfängt jede gesendete Nachricht erneut über die mqtt in-Node.
Das bedeutet:
👉 Egal ob die Nachricht vom Browser, einem ESP32 oder dem MQTT Explorer kommt – sie wird immer gleich verarbeitet.
Speicherung in der SQLite-Datenbank
Die empfangene Nachricht wird anschließend in der Datenbank gespeichert.
Dazu wird in einer Function-Node ein SQL-Statement erzeugt:
function esc(str) {
return String(str).replace(/'/g, "''");
}
const name = esc(msg.payload.name);
const message = esc(msg.payload.message);
const zeit = esc(msg.payload.timestamp);
msg.topic = `INSERT INTO chat_messages (name, message, zeit) VALUES ('${name}', '${message}', ${zeit});`;
return msg;
👉 Die SQLite-Node führt dieses Statement aus und speichert die Nachricht persistent.
Chat-Historie laden
Damit der Benutzer alle bisherigen Nachrichten sieht, wird nach jedem Event die Historie neu geladen.
Das erfolgt über folgende SQL-Abfrage:
SELECT
id,
name,
message,
strftime('%d.%m.%Y %H:%M:%S', zeit / 1000, 'unixepoch', 'localtime') AS zeit_format
FROM chat_messages ORDER BY zeit ASC;
👉 Hier passiert etwas Wichtiges:
- Der gespeicherte UNIX-Timestamp wird direkt in ein lesbares Datum umgewandelt
- Format:
Tag.Monat.Jahr Stunde:Minute:Sekunde
Steuerung über Topics
Im Flow wird zwischen zwei Aktionen unterschieden:
load_chat→ lädt die Historiesend_message→ sendet eine neue Nachricht
Das erfolgt über eine switch-Node:
👉 Dadurch bleibt der Flow übersichtlich und klar strukturiert.
Warum funktioniert das Ganze so gut mit MQTT?
Ganz einfach:
MQTT ist von Grund auf für verteilte Systeme gebaut.
Jeder Teilnehmer kann:
- Nachrichten senden (Publisher)
- Nachrichten empfangen (Subscriber)
👉 Und genau das macht einen Chat aus.
Der große Vorteil:
Neue Teilnehmer können jederzeit hinzugefügt werden, ohne dass die bestehende Architektur angepasst werden muss.
Ob Browser, ESP32 oder ein eigenes Script:
Solange das richtige Topic verwendet wird, ist jeder sofort Teil des Systems.
Nachrichten mit anderen MQTT-Clients senden
Ein großer Vorteil dieses Aufbaus ist, dass der Chat nicht fest an das Node-RED Dashboard gebunden ist.
Da alle Nachrichten über das MQTT-Topic chat/message laufen, kann grundsätzlich jeder MQTT-fähige Client an der Kommunikation teilnehmen. Das kann ein weiteres Node-RED Dashboard sein, ein ESP32, ein ESP8266 oder auch ein externes MQTT-Tool wie CorreoMQTT.
Wichtig ist nur, dass die Nachricht im erwarteten JSON-Format gesendet wird:
{
"name": "CorreoMQTT",
"message": "Hallo aus einem externen MQTT-Client!",
"timestamp": 1715779200000
}
Die Nachricht wird anschließend vom Node-RED Subscriber empfangen, in der SQLite-Datenbank gespeichert und beim erneuten Laden der Historie im Dashboard angezeigt.
Damit wird sehr schön sichtbar, worum es bei MQTT eigentlich geht:
👉 Der Absender muss das Dashboard nicht kennen.
👉 Der Empfänger muss den Absender nicht kennen.
👉 Beide müssen nur dasselbe MQTT-Topic verwenden.
Für unser Beispiel lautet dieses Topic:
chat/message
Dadurch lässt sich der Chat sehr einfach erweitern. So könnte später zum Beispiel ein ESP32 automatisch Statusmeldungen senden oder ein anderes System Benachrichtigungen in den Chat schreiben.
Fazit
Mit diesem Projekt hast du gesehen, dass MQTT deutlich mehr kann als nur Sensordaten oder Schaltbefehle zwischen Geräten auszutauschen.
Mit überraschend wenig Aufwand haben wir einen einfachen Chat aufgebaut, der Nachrichten in Echtzeit verteilt, dauerhaft speichert und unabhängig vom eigentlichen Client funktioniert.
Dabei übernehmen die einzelnen Komponenten klar getrennte Aufgaben:
- MQTT verteilt die Nachrichten
- Node-RED übernimmt Oberfläche und Logik
- SQLite speichert die Historie dauerhaft
Genau diese Trennung macht den Aufbau so flexibel.
Das Spannende daran:
Der Chat selbst ist am Ende nur ein Beispiel.
Denn technisch betrachtet unterscheiden sich Chatnachrichten kaum von Sensordaten, Statusmeldungen oder Befehlen aus einem Smart-Home-System.
Heute schreibt ein Benutzer eine Nachricht.
Morgen sendet ein ESP32 eine Warnung.
Übermorgen meldet ein Shelly automatisch einen Zustand.
Und genau hier zeigt sich die eigentliche Stärke von MQTT:
Nicht die Nachricht ist entscheidend – sondern dass jeder Teilnehmer dieselbe Sprache spricht.
Ich hoffe, ich konnte dir mit diesem Projekt zeigen, dass MQTT weit mehr ist als nur ein Protokoll für Temperaturwerte.
Vielleicht ist dieser kleine Chat ja der Einstieg in dein nächstes größeres IoT-Projekt.
Letzte Aktualisierung am: 24. Mai 2026














