Tutoriels IoT

Tutoriel MQTT.js avec Node.js : publication, abonnement, QoS et TLS

Créez un publisher et un subscriber Node.js avec MQTT.js v5, utilisez l'API asynchrone, la QoS et le Last Will, chiffrez avec TLS sur le port 8883 et réglez les options de reconnexion pour que votre service survive aux coupures réseau.

Sortie de terminal Node.js d'un publisher et d'un subscriber MQTT.js connectés à un broker cloud via TLS
Essayez-le sur votre propre broker MQTTObtenez un broker MQTT privé et managé avec l’offre Free et partez d’exemples de code prêts à l’emploi.
Essayer gratuitement Exemples de code Aucune carte bancaire requise

MQTT.js est le client MQTT de référence pour JavaScript, et utiliser MQTT.js avec Node.js est le moyen le plus rapide de construire des backends IoT, des passerelles et des outils de test. Dans ce tutoriel, vous allez créer un publisher et un subscriber Node.js avec MQTT.js v5, utiliser son API à base de promesses, choisir les niveaux de QoS, ajouter un Last Will, passer à TLS et configurer la reconnexion pour un service de longue durée. Tous les exemples tournent sur le serveur MQTT public gratuit broker.connectmq.com : il vous suffit d'avoir Node.js installé.

Prérequis

  • Node.js 18 ou plus récent (node --version)
  • npm, pnpm ou yarn
  • Facultatif : mosquitto_sub pour observer le trafic depuis un second terminal

Créez un projet et installez la bibliothèque :

mkdir mqtt-node-demo && cd mqtt-node-demo
npm init -y
npm pkg set type=module
npm install mqtt

Définir "type": "module" vous permet d'utiliser la syntaxe import et le await de premier niveau. MQTT.js v5 est écrit en TypeScript et embarque ses propres définitions de types : les éditeurs proposent donc l'autocomplétion sans paquet supplémentaire.

URL de connexion et ports

MQTT.js choisit le transport d'après le schéma de l'URL. Sur les brokers ConnectMQ, les ports sont les suivants :

Schéma Port Transport Usage typique en Node.js
mqtt:// 1883 TCP en clair Tests locaux uniquement
mqtts:// 8883 TCP avec TLS Services de production
ws:// 8083 WebSocket Rarement utile en Node.js
wss:// 8084 (chemin /mqtt) WebSocket sécurisé Navigateurs, proxys restrictifs

En Node.js, privilégiez mqtts:// sur le port 8883. Le WebSocket compte surtout pour les navigateurs, qui ne peuvent pas ouvrir de sockets TCP bruts ; l'article MQTT over WebSocket traite de cet aspect.

Votre premier subscriber MQTT.js

Enregistrez sous subscriber.js :

import mqtt from "mqtt";

const PREFIX = "connectmq/demo/node-a71c";
const clientId = `node-sub-${Math.random().toString(16).slice(2, 10)}`;

const client = mqtt.connect("mqtts://broker.connectmq.com:8883", {
  clientId,
  protocolVersion: 5,
  clean: true,
  keepalive: 30,
  reconnectPeriod: 2000,   // attendre 2 s entre deux tentatives de reconnexion
  connectTimeout: 10000,   // abandonner une tentative après 10 s
});

client.on("connect", async (connack) => {
  console.log(`Connected as ${clientId}, session present: ${connack.sessionPresent}`);
  const granted = await client.subscribeAsync(`${PREFIX}/+/telemetry`, { qos: 1 });
  console.log("Subscribed:", granted.map((g) => `${g.topic} (qos ${g.qos})`).join(", "));
});

client.on("message", (topic, payload, packet) => {
  let data;
  try {
    data = JSON.parse(payload.toString());
  } catch {
    data = payload.toString();
  }
  const device = topic.split("/").at(-2);
  console.log(`[${device}] qos=${packet.qos} retain=${packet.retain}`, data);
});

client.on("reconnect", () => console.log("Reconnecting..."));
client.on("offline", () => console.log("Offline"));
client.on("error", (err) => console.error("MQTT error:", err.message));

process.on("SIGINT", async () => {
  await client.endAsync();
  process.exit(0);
});

S'abonner dans le handler connect est un choix délibéré. Avec clean: true, le broker oublie les abonnements quand la connexion tombe : se réabonner à chaque connexion garantit que vous continuez à recevoir les messages après une reconnexion.

Publier avec QoS et Last Will

Enregistrez sous publisher.js :

import mqtt from "mqtt";

const PREFIX = "connectmq/demo/node-a71c";
const DEVICE = "sensor-01";
const clientId = `node-pub-${Math.random().toString(16).slice(2, 10)}`;

const client = await mqtt.connectAsync("mqtts://broker.connectmq.com:8883", {
  clientId,
  protocolVersion: 5,
  will: {
    topic: `${PREFIX}/${DEVICE}/status`,
    payload: Buffer.from("offline"),
    qos: 1,
    retain: true,
  },
});

console.log("Connected");
await client.publishAsync(`${PREFIX}/${DEVICE}/status`, "online", { qos: 1, retain: true });

let count = 0;
const timer = setInterval(async () => {
  const reading = {
    temperature: +(20 + Math.random() * 5).toFixed(2),
    humidity: +(40 + Math.random() * 10).toFixed(1),
    ts: Date.now(),
  };
  try {
    await client.publishAsync(`${PREFIX}/${DEVICE}/telemetry`, JSON.stringify(reading), {
      qos: 1,
      properties: { messageExpiryInterval: 60, contentType: "application/json" },
    });
    console.log("Published", reading);
  } catch (err) {
    console.error("Publish failed:", err.message);
  }
  if (++count === 20) {
    clearInterval(timer);
    await client.publishAsync(`${PREFIX}/${DEVICE}/status`, "offline", { qos: 1, retain: true });
    await client.endAsync();
  }
}, 2000);

Lancez les deux :

node subscriber.js
# dans un second terminal
node publisher.js

Plusieurs choses se passent ici :

  • connectAsync se résout dès que le broker envoie le CONNACK, ce qui est pratique dans les scripts. Dans les services de longue durée, l'événement connect reste utile car il se déclenche à nouveau après chaque reconnexion.
  • La QoS 1 signifie que le broker accuse réception de chaque publication, et publishAsync ne se résout qu'après cet accusé. Pour de la télémétrie à haut débit où une perte occasionnelle est acceptable, la QoS 0 coûte moins cher. Les compromis sont détaillés dans les niveaux de QoS MQTT expliqués.
  • Le Last Will est enregistré par le broker à la connexion et publié si le client disparaît sans déconnexion propre, afin que les autres services apprennent que le capteur est hors ligne. Le rendre retained permet aux nouveaux abonnés de voir immédiatement le statut actuel.
  • Les propriétés MQTT 5 comme messageExpiryInterval demandent au broker d'abandonner le message s'il ne peut pas être livré dans les 60 secondes, ce qui évite l'accumulation de mesures périmées pour les abonnés hors ligne.

L'espace public du serveur MQTT public gratuit est lisible par tout le monde : remplacez la partie node-a71c du préfixe par votre propre chaîne aléatoire et n'y envoyez jamais de données réelles.

Les options de reconnexion qui comptent

MQTT.js se reconnecte automatiquement. Ces options déterminent son comportement :

Option Valeur par défaut Recommandation
reconnectPeriod 1000 ms 2000 à 5000 ms pour les services ; 0 désactive la reconnexion
connectTimeout 30000 ms 10000 ms pour détecter plus vite un réseau mort
keepalive 60 s 30 à 60 s ; une valeur plus basse détecte plus tôt les coupures silencieuses
clean true false avec un client ID stable si vous avez besoin des messages en file d'attente
properties.sessionExpiryInterval 0 Secondes pendant lesquelles le broker conserve la session après déconnexion (MQTT 5)
queueQoSZero true Mettez false si les messages QoS 0 périmés ne doivent pas être envoyés après reconnexion

Pour un service qui ne doit manquer aucun message pendant une brève absence, combinez un client ID fixe, clean: false, des abonnements en QoS 1 et un intervalle d'expiration de session. Le broker met alors les messages en file d'attente pour la session et les livre au retour du client.

Structurer un service Node.js de longue durée

Les scripts ci-dessus sont volontairement courts. Un service qui tourne pendant des mois a besoin d'un peu plus de structure, et l'essentiel concerne le cycle de vie plutôt que MQTT lui-même :

  • Créez un seul client par processus et partagez-le via un module. Ouvrir une nouvelle connexion par requête ou par tâche gaspille des ressources et atteint vite les limites de connexions.
  • Considérez connect comme le signal « prêt ». Conditionnez le travail qui nécessite le broker à la propriété connected du client, et décidez quoi faire quand elle vaut false : mettre en tampon, abandonner ou renvoyer une erreur à l'appelant.
  • Gérez les signaux d'arrêt. Sur SIGTERM (envoyé par Docker, Kubernetes et systemd), publiez un statut final si vous en utilisez un, appelez endAsync() pour que les messages QoS 1 en cours soient vidés, puis seulement quittez.
  • Journalisez les transitions d'état, pas chaque message. Les événements connect, reconnect, offline, close et error sont ceux dont vous avez besoin pour diagnostiquer une panne. Des logs par message les noient.
  • Gardez des handlers rapides. L'événement message s'exécute sur l'event loop de Node. Les traitements lourds, comme les écritures en base, doivent être attendus avec soin ou placés dans une file afin qu'un système aval lent ne retarde pas tous les autres messages.

Ces habitudes comptent davantage que la valeur de n'importe quelle option, et elles s'appliquent aussi bien aux passerelles, aux workers qu'aux outils de test construits sur MQTT.js.

S'authentifier sur un broker privé

En production, utilisez les identifiants de votre broker. Avec ConnectMQ, vous créez un broker dans le portail et recevez un nom d'utilisateur, un mot de passe et un client ID. Chargez-les depuis des variables d'environnement :

import mqtt from "mqtt";

const client = mqtt.connect(`mqtts://${process.env.MQTT_HOST}:8883`, {
  clientId: process.env.MQTT_CLIENT_ID,   // YOUR_CLIENT_ID depuis le portail
  username: process.env.MQTT_USERNAME,    // YOUR_USERNAME
  password: process.env.MQTT_PASSWORD,    // YOUR_PASSWORD
  protocolVersion: 5,
  clean: false,
  properties: { sessionExpiryInterval: 3600 },
});

client.on("connect", () => console.log("Connected to private broker"));
client.on("error", (err) => console.error(err.message));

ConnectMQ utilise un certificat Let's Encrypt, que Node.js reconnaît nativement : vous n'avez pas besoin de fournir de fichier CA. Ne définissez jamais rejectUnauthorized: false en production ; cela désactive la vérification des certificats et rend TLS inutile.

Dépannage de MQTT.js sous Node.js

  1. Connexion, puis déconnexion toutes les quelques secondes. Un autre client utilise le même client ID. Chaque processus en cours d'exécution a besoin de son propre ID.
  2. ERR_REQUIRE_ESM ou Cannot use import statement. Définissez "type": "module" dans package.json, ou utilisez const mqtt = require("mqtt") dans les fichiers CommonJS.
  3. Plus aucun message après une coupure réseau. Vous ne vous êtes abonné qu'une seule fois, en dehors du handler connect, tout en utilisant clean: true.
  4. connack timeout. Un pare-feu bloque le port 8883. Testez avec mosquitto_sub -h broker.connectmq.com -p 8883 --capath /etc/ssl/certs -i test-1 -t test ou essayez wss:// sur le port 8084.
  5. Abonnement accordé avec une QoS 128 ou un reason code de 0x80 ou plus. Le broker a refusé l'abonnement, souvent à cause d'un filtre invalide ou de la limite d'abonnements du serveur MQTT public gratuit (10 par client).
  6. La mémoire augmente hors ligne. Les messages sortants sont mis en file jusqu'à la reconnexion. Plafonnez votre propre tampon ou utilisez la QoS 0 avec queueQoSZero: false pour la télémétrie jetable.

Étapes suivantes

À partir de là, vous pouvez réutiliser le même client dans un frontend : React avec MQTT construit un tableau de bord à base de hooks sur la version navigateur de MQTT.js, et MQTT typé avec TypeScript ajoute des topics typés et des payloads validés par-dessus ce code. Pour les autres environnements d'exécution, le guide des bibliothèques clientes MQTT compare les principales options. La liste complète des options se trouve dans le README de MQTT.js.

Démarrer avec ConnectMQ

Faites passer vos services Node.js du serveur MQTT public gratuit à un broker privé en quelques minutes. Inscrivez-vous sur app.connectmq.com, choisissez l'offre Serverless gratuite sans carte bancaire, créez un broker et placez les identifiants générés dans votre environnement. Avec un broker MQTT managé, ConnectMQ gère les endpoints TLS et l'infrastructure du broker pendant que vous vous concentrez sur le code de votre application.

Questions fréquentes

MQTT.js v5 est-il compatible avec du code plus ancien ?

En grande partie. L'API à callbacks fonctionne toujours, et la v5 ajoute des méthodes à base de promesses comme connectAsync, publishAsync, subscribeAsync et endAsync. Les principaux changements sont la réécriture en TypeScript et une version minimale de Node.js plus récente.

Faut-il utiliser MQTT 3.1.1 ou MQTT 5 avec MQTT.js ?

Utilisez protocolVersion: 5 lorsque votre broker le prend en charge, comme c'est le cas de ConnectMQ. Vous bénéficiez des reason codes, de l'expiration des messages et des sessions et des user properties, tandis que le code de publication et d'abonnement reste identique.

Combien de connexions un service Node.js doit-il ouvrir ?

Généralement une seule. Un unique client MQTT.js peut publier et s'abonner à de nombreux topics en parallèle, et chaque connexion supplémentaire nécessite son propre client ID unique et compte dans la limite de connexions de votre offre.

Peut-on utiliser MQTT.js avec Bun ou Deno ?

MQTT.js cible Node.js et les navigateurs. Les environnements d'exécution offrant une bonne compatibilité Node.js peuvent souvent le faire tourner, mais testez soigneusement le comportement de TLS et de la reconnexion avant de vous y fier.

Articles liés

Les meilleurs outils CLI MQTT pour tester, scripter et déboguer

Un comparatif pratique de mosquitto_pub/sub, MQTTX CLI, HiveMQ MQTT CLI, la CLI MQTT.js, curl et mqttui, avec le même test exécuté sur le serveur MQTT public gratuit broker.connectmq.com pour choisir le bon outil pour le scripting et le débogage.

9 min de lecture

Tous les articles →

Votre broker MQTT privé, à quelques clics

Créez un compte, récupérez les identifiants de votre broker et connectez votre premier appareil — aucun serveur à installer, mettre à jour ou dimensionner.