MQTT.js ist der meistgenutzte MQTT-Client für JavaScript. Dasselbe Paket läuft in Node.js (über TCP, TLS oder WebSockets) und im Browser (über WebSockets), unterstützt MQTT 3.1.1 und 5 und bringt seit Version 5 TypeScript-Typen und Promise-basierte Methoden mit. Dieses Tutorial deckt die v5-API von Anfang bis Ende ab.
MQTT.js installieren
npm install mqttDie Beispiele nutzen ES-Module (import) und die öffentliche HiveMQ-Sandbox. Speichere sie als .mjs-Dateien oder setze "type": "module" in der package.json.
Verbindungs-URLs: welches Protokoll wo
| URL-Schema | Transport | Typischer Port | Node.js | Browser |
|---|---|---|---|---|
mqtt:// | Reines TCP | 1883 | Ja | Nein |
mqtts:// | TLS | 8883 | Ja | Nein |
ws:// | WebSocket | 8000, 8083, 80 | Ja | Nur auf http://-Seiten |
wss:// | Sicherer WebSocket | 8884, 8084, 443 | Ja | Ja |
Ports und WebSocket-Pfade unterscheiden sich je nach Broker – sieh dir die Seiten zu HiveMQ oder EMQX an oder lies MQTT-Ports erklärt.
Subscriben und publishen in Node.js
Die klassische eventbasierte API: mqtt.connect() gibt sofort einen Client zurück und feuert Events, während die Verbindung aufgebaut wird.
import mqtt from 'mqtt';
const topic = 'testmqtt/js-demo/hello';
const client = mqtt.connect('mqtt://broker.hivemq.com:1883', {
clientId: `js-demo-${Math.random().toString(16).slice(2, 10)}`,
clean: true,
keepalive: 60,
reconnectPeriod: 2000, // ms between reconnect attempts; 0 disables
connectTimeout: 10_000,
});
client.on('connect', () => {
console.log('connected');
client.subscribe('testmqtt/js-demo/#', { qos: 1 }, (err) => {
if (err) return console.error('subscribe failed:', err.message);
client.publish(topic, JSON.stringify({ hello: 'world' }), { qos: 1 });
});
});
client.on('message', (topic, payload, packet) => {
console.log(`${topic} (retain=${packet.retain}): ${payload.toString()}`);
});
client.on('reconnect', () => console.log('reconnecting...'));
client.on('offline', () => console.log('offline'));
client.on('error', (err) => console.error('error:', err.message));payload ist ein Buffer – ruf toString() für Text oder JSON.parse(payload.toString()) für JSON auf. Das connect-Event wird nach jedem Reconnect erneut ausgelöst; mit clean: true vergisst der Broker deine Subscriptions, deshalb ist das Subscriben im Handler das sichere Muster. MQTT.js subscribt außerdem standardmäßig beim Reconnect neu (resubscribe: true).
Async/await mit connectAsync
Für Skripte, Tests und Serverless-Funktionen liest sich die Promise-API von MQTT.js v5 deutlich natürlicher:
import mqtt from 'mqtt';
const client = await mqtt.connectAsync('mqtts://broker.hivemq.com:8883', {
protocolVersion: 5,
});
client.on('message', (topic, payload) => {
console.log(topic, payload.toString());
});
try {
const granted = await client.subscribeAsync('testmqtt/js-demo/#', { qos: 1 });
console.log('granted:', granted.map((g) => `${g.topic} QoS ${g.qos}`).join(', '));
await client.publishAsync('testmqtt/js-demo/async', 'sent with publishAsync', {
qos: 1,
retain: false,
});
await new Promise((resolve) => setTimeout(resolve, 2000));
} finally {
await client.endAsync();
}connectAsync() wird abgelehnt, wenn der Client vor dem Verbinden ein error auslöst – etwa wenn der Broker deine Zugangsdaten ablehnt –, sodass du das mit einem normalen try/catch behandeln kannst. Bei einem nicht erreichbaren Host versucht es standardmäßig immer weiter; übergib false als drittes Argument, um stattdessen sofort zu scheitern. publishAsync() wird aufgelöst, sobald der Broker eine QoS-1- oder QoS-2-Nachricht bestätigt hat. Was jede Stufe garantiert, steht unter MQTT-QoS-Stufen.
Deine MQTT.js-Nachrichten live ansehen
TLS, Zugangsdaten und MQTT-5-Optionen in Node.js
Produktions-Broker verlangen TLS und Authentifizierung. Die Zugangsdaten kommen in die Connect-Optionen; bei einem Broker mit öffentlich vertrauenswürdigem Zertifikat reicht mqtts://. Bei einer privaten CA übergibst du das CA-Zertifikat (und bei Mutual TLS zusätzlich Client-Zertifikat und Schlüssel):
import { readFileSync } from 'node:fs';
import mqtt from 'mqtt';
const client = await mqtt.connectAsync('mqtts://broker.example.com:8883', {
clientId: 'billing-service-01',
username: process.env.MQTT_USERNAME,
password: process.env.MQTT_PASSWORD,
protocolVersion: 5,
clean: false,
properties: { sessionExpiryInterval: 3600 },
// Only for a private CA / mutual TLS:
// ca: readFileSync('ca.crt'),
// cert: readFileSync('client.crt'),
// key: readFileSync('client.key'),
will: {
topic: 'services/billing/status',
payload: Buffer.from('offline'),
qos: 1,
retain: true,
},
});
await client.publishAsync('services/billing/status', 'online', { qos: 1, retain: true });
await client.publishAsync('orders/created', JSON.stringify({ id: 42 }), {
qos: 1,
properties: {
contentType: 'application/json',
messageExpiryInterval: 60,
userProperties: { source: 'billing' },
},
});Setze rejectUnauthorized: false niemals außerhalb lokaler Tests – es schaltet die Zertifikatsprüfung ab. Halte Zugangsdaten in Umgebungsvariablen statt im Quellcode. Die Option will registriert einen Last Will, den der Broker publisht, wenn der Service ohne Disconnect stirbt, und retain: true hält den aktuellen Status für neue Subscriber bereit (siehe Retained Messages). Ob du die MQTT-5-Properties überhaupt brauchst, klärt MQTT 5 vs. 3.1.1.
Sauber herunterfahren
Ein langlaufender Node.js-Service sollte sich sauber trennen, damit laufende QoS-1/2-Nachrichten abgeschlossen werden und der Broker bei einem normalen Neustart nicht deinen Last Will auslöst:
process.on('SIGTERM', async () => {
await client.publishAsync('services/billing/status', 'offline', { qos: 1, retain: true });
await client.endAsync();
process.exit(0);
});MQTT.js im Browser
Im Browser funktionieren nur WebSocket-URLs. Auf einer HTTPS-Seite musst du wss:// nutzen, sonst blockiert der Browser die Verbindung als Mixed Content:
import mqtt from 'mqtt';
const client = mqtt.connect('wss://broker.hivemq.com:8884/mqtt', {
clientId: `web-${crypto.randomUUID().slice(0, 8)}`,
});
client.on('connect', () => client.subscribe('testmqtt/js-demo/#'));
client.on('message', (topic, payload) => {
const li = document.createElement('li');
li.textContent = `${topic}: ${payload.toString()}`;
document.querySelector('#messages').append(li);
});Moderne Bundler wie Vite, webpack 5 und der Next.js-Compiler greifen automatisch auf den Browser-Build zurück, wenn du import mqtt from 'mqtt' schreibst. Beschwert sich ein Bundler über Node-Built-ins, importiere stattdessen das vorgefertigte Bundle aus mqtt/dist/mqtt.min.js. Ohne Bundler lädst du dieselbe Datei per <script>-Tag von einem CDN und nutzt das globale mqtt-Objekt. Der Leitfaden zu MQTT über WebSockets behandelt Pfade und Ports im Detail.
MQTT.js in React nutzen
Der häufigste Bug: bei jedem Render einen neuen Client erzeugen. Erzeuge den Client einmal in useEffect und beende ihn immer im Cleanup – React StrictMode mountet Effects in der Entwicklung doppelt, und ohne Cleanup bekommst du doppelte Verbindungen und doppelte Nachrichten:
import { useEffect, useState } from 'react';
import mqtt from 'mqtt';
export function useMqttMessages(url: string, topic: string) {
const [messages, setMessages] = useState<string[]>([]);
useEffect(() => {
const client = mqtt.connect(url);
client.on('connect', () => client.subscribe(topic));
client.on('message', (_t, payload) => {
setMessages((prev) => [...prev.slice(-99), payload.toString()]);
});
return () => {
client.end();
};
}, [url, topic]);
return messages;
}In Next.js nutzt MQTT.js Browser-APIs; ruf es also nur aus Client Components ('use client') auf oder aus serverseitigen Route Handlern mit mqtt:// bzw. mqtts://. In größeren Apps legst du einen einzigen Client in einen Context Provider und teilst ihn.
Fehlerbehandlung
- Häng immer einen
error-Listener an. In Node.js bringt ein unbehandelteserror-Event den Prozess zum Absturz. - Falsche Zugangsdaten führen zu Endlos-Retries. Bei der Event-API löst ein abgelehntes CONNECT
erroraus, und MQTT.js versucht es immer wieder. Prüfeerr.codeund rufclient.end()auf, wenn weitere Versuche sinnlos sind. Codes schlägst du mit dem MQTT-Reason-Code-Tool nach. - Endlose Reconnect-Schleifen mit sofortigem Disconnect bedeuten meist eine doppelte Client-ID oder eine ACL, die deine Subscription ablehnt. Siehe den Leitfaden zu MQTT-Verbindungsfehlern.
- Offline-Pufferung. Solange die Verbindung getrennt ist, puffert MQTT.js Publishes im Speicher und sendet sie beim Reconnect. Setze
queueQoSZero: false, wenn veraltete QoS-0-Daten stattdessen verworfen werden sollen.
Brauchst du einen Ausgangspunkt für deinen eigenen Broker? Der MQTT-Code-Generator erzeugt diesen Code für deinen Host, Port, deine Zugangsdaten und dein Topic – teste ihn dann live im Online-MQTT-Client.
Häufig gestellte Fragen
Kann sich MQTT.js im Browser mit Port 1883 verbinden?
Nein. Browser können keine rohen TCP-Sockets öffnen, daher muss MQTT.js im Browser eine ws://- oder wss://-URL nutzen, die auf den WebSocket-Listener des Brokers zeigt, zum Beispiel wss://broker.hivemq.com:8884/mqtt. In Node.js kannst du mqtt://, mqtts://, ws:// oder wss:// verwenden.
Wie nutze ich async/await mit MQTT.js?
MQTT.js v5 bietet Promise-basierte Methoden: mqtt.connectAsync() zum Verbinden sowie client.subscribeAsync(), client.publishAsync() und client.endAsync() auf dem zurückgegebenen Client. Eingehende Nachrichten kommen weiterhin über das message-Event.
Warum öffnet meine React-App zwei MQTT-Verbindungen?
React StrictMode mountet Komponenten in der Entwicklung doppelt, daher erzeugt ein Effect, der ohne Cleanup verbindet, zwei Clients. Erzeuge den Client in useEffect und rufe client.end() in der Cleanup-Funktion auf, oder halte einen einzigen Client in einem Modul oder Context.