Tutorials

MQTT in JavaScript: MQTT.js-Tutorial für Node.js und den Browser

Lerne MQTT in JavaScript mit MQTT.js v5: verbinden, subscriben und publishen in Node.js und im Browser über WebSockets, mit async/await, React und Fehlerbehandlung.

Aktualisiert · 8 Min. Lesezeit

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

bash
npm install mqtt

Die 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-SchemaTransportTypischer PortNode.jsBrowser
mqtt://Reines TCP1883JaNein
mqtts://TLS8883JaNein
ws://WebSocket8000, 8083, 80JaNur auf http://-Seiten
wss://Sicherer WebSocket8884, 8084, 443JaJa

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.

client.mjs
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:

async.mjs
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):

secure.mjs
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:

javascript
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:

browser.js
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:

useMqtt.ts
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 unbehandeltes error-Event den Prozess zum Absturz.
  • Falsche Zugangsdaten führen zu Endlos-Retries. Bei der Event-API löst ein abgelehntes CONNECT error aus, und MQTT.js versucht es immer wieder. Prüfe err.code und ruf client.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.