Tutoriels

MQTT en JavaScript : tutoriel MQTT.js pour Node.js et le navigateur

Apprenez MQTT en JavaScript avec MQTT.js v5 : connexion, abonnement et publication dans Node.js et le navigateur via WebSockets, async/await, React et erreurs.

Mis à jour · 8 min de lecture

MQTT.js est le client MQTT le plus utilisé en JavaScript. Le même paquet fonctionne dans Node.js (sur TCP, TLS ou WebSockets) et dans le navigateur (sur WebSockets), prend en charge MQTT 3.1.1 et 5 et, depuis la version 5, embarque des types TypeScript et des méthodes à base de promesses. Ce tutoriel couvre l’API v5 de bout en bout.

Installer MQTT.js

bash
npm install mqtt

Les exemples utilisent les modules ES (import) et le bac à sable public HiveMQ. Enregistrez-les en fichiers .mjs ou définissez "type": "module" dans package.json.

URL de connexion : quel protocole, où

Schéma d’URLTransportPort habituelNode.jsNavigateur
mqtt://TCP en clair1883OuiNon
mqtts://TLS8883OuiNon
ws://WebSocket8000, 8083, 80OuiUniquement sur des pages http://
wss://WebSocket sécurisé8884, 8084, 443OuiOui

Les ports et chemins WebSocket varient selon le broker — consultez les pages HiveMQ ou EMQX, ou lisez les ports MQTT expliqués.

S’abonner et publier dans Node.js

L’API classique à base d’événements : mqtt.connect() renvoie immédiatement un client et émet des événements au fil de la connexion.

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 est un Buffer — appelez toString() pour du texte ou JSON.parse(payload.toString()) pour du JSON. L’événement connect se déclenche de nouveau après chaque reconnexion ; avec clean: true, le broker oublie vos abonnements, d’où l’intérêt de s’abonner dans le handler. MQTT.js se réabonne aussi par défaut à la reconnexion (resubscribe: true).

Async/await avec connectAsync

Pour les scripts, les tests et les fonctions serverless, l’API à promesses de MQTT.js v5 se lit bien plus naturellement :

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() est rejetée lorsque le client émet une error avant d’être connecté — par exemple quand le broker refuse vos identifiants — vous pouvez donc la gérer avec un simple try/catch. Pour un hôte injoignable, elle continue par défaut de réessayer ; passez false en troisième argument pour échouer immédiatement. publishAsync() se résout dès que le broker acquitte un message QoS 1 ou 2. Voir les niveaux de QoS MQTT pour ce que garantit chaque niveau.

Voir vos messages MQTT.js en direct

TLS, identifiants et options MQTT 5 dans Node.js

Les brokers de production exigent TLS et une authentification. Les identifiants vont dans les options de connexion ; pour un broker doté d’un certificat reconnu publiquement, mqtts:// suffit. Pour une CA privée, passez le certificat de la CA (et, pour le TLS mutuel, un certificat et une clé client) :

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' },
  },
});

Ne définissez jamais rejectUnauthorized: false en dehors de tests locaux — cela désactive la vérification des certificats. Gardez les identifiants dans des variables d’environnement plutôt que dans le code source. L’option will enregistre un Last Will que le broker publie si le service meurt sans se déconnecter, et retain: true garde le dernier statut disponible pour les nouveaux abonnés (voir les messages retenus). Savoir si vous avez vraiment besoin des propriétés MQTT 5 est traité dans MQTT 5 vs 3.1.1.

Arrêter proprement

Un service Node.js de longue durée doit se déconnecter proprement pour que les messages QoS 1/2 en cours aboutissent et que le broker ne déclenche pas votre Last Will lors d’un redémarrage normal :

javascript
process.on('SIGTERM', async () => {
  await client.publishAsync('services/billing/status', 'offline', { qos: 1, retain: true });
  await client.endAsync();
  process.exit(0);
});

MQTT.js dans le navigateur

Dans le navigateur, seules les URL WebSocket fonctionnent. Sur une page HTTPS, vous devez utiliser wss://, sinon le navigateur bloque la connexion comme contenu mixte :

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);
});

Les bundlers modernes comme Vite, webpack 5 et le compilateur de Next.js choisissent la version navigateur lorsque vous écrivez import mqtt from 'mqtt'. Si un bundler se plaint des modules natifs de Node, importez plutôt le bundle précompilé mqtt/dist/mqtt.min.js. Sans bundler, chargez ce même fichier depuis un CDN avec une balise <script> et utilisez l’objet global mqtt. Le guide MQTT sur WebSockets détaille chemins et ports.

Utiliser MQTT.js dans React

Le bug le plus courant consiste à créer un nouveau client à chaque rendu. Créez le client une seule fois dans useEffect et terminez-le toujours dans la fonction de nettoyage — React StrictMode monte les effets deux fois en développement, et sans nettoyage vous obtenez des connexions et des messages en double :

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;
}

Dans Next.js, MQTT.js utilise des API du navigateur : appelez-le uniquement depuis des Client Components ('use client') ou depuis des route handlers côté serveur en mqtt:// ou mqtts://. Pour les applications plus importantes, placez un client unique dans un context provider et partagez-le.

Gestion des erreurs

  • Attachez toujours un listener error. Dans Node.js, un événement error non géré fait planter le processus.
  • De mauvais identifiants provoquent des tentatives sans fin. Avec l’API à événements, un CONNECT refusé émet error et MQTT.js continue de se reconnecter. Inspectez err.code et appelez client.end() si réessayer est inutile. Retrouvez les codes avec l’outil des codes de raison MQTT.
  • Des boucles de reconnexion infinies avec déconnexion immédiate signifient généralement un client ID en double ou une ACL qui rejette votre abonnement. Voir le guide des erreurs de connexion MQTT.
  • Mise en tampon hors ligne. Pendant une déconnexion, MQTT.js met les publications en file en mémoire et les envoie à la reconnexion. Définissez queueQoSZero: false si les données QoS 0 périmées doivent plutôt être abandonnées.

Besoin d’un point de départ pour votre propre broker ? Le générateur de code MQTT produit ce code pour votre hôte, votre port, vos identifiants et votre topic — testez-le ensuite en direct dans le client MQTT en ligne.

Questions fréquentes

MQTT.js peut-il se connecter au port 1883 depuis le navigateur ?

Non. Les navigateurs ne peuvent pas ouvrir de sockets TCP bruts : dans le navigateur, MQTT.js doit utiliser une URL ws:// ou wss:// pointant vers le listener WebSocket du broker, par exemple wss://broker.hivemq.com:8884/mqtt. Dans Node.js, vous pouvez utiliser mqtt://, mqtts://, ws:// ou wss://.

Comment utiliser async/await avec MQTT.js ?

MQTT.js v5 fournit des méthodes à base de promesses : mqtt.connectAsync() pour se connecter, puis client.subscribeAsync(), client.publishAsync() et client.endAsync() sur le client renvoyé. Les messages entrants sont toujours livrés via l’événement message.

Pourquoi mon application React ouvre-t-elle deux connexions MQTT ?

React StrictMode monte les composants deux fois en développement : un effet qui se connecte sans fonction de nettoyage crée donc deux clients. Créez le client dans useEffect et appelez client.end() dans le nettoyage, ou conservez un client unique dans un module ou un context.