As we may already know, Node.js is an async and event-driven JavaScript runtime and engine, powering lots of the server-side, networked applications that exist today. In this post, we are going to explore an interplay of Node.js with MQTT, a publish/subscribe (pub/sub) protocol and standard for the Internet of Things (IoT) world.
In this post, we plan to cover just the important, public MQTT APIs and functions, and also explore a simple publisher and subscriber script in Node.js.
In 1999, Andy Standford-Clark of IBM and Arlen Nipper designed the initial version of the MQTT protocol. At that time, the goal was to build a protocol that could support low bandwidth, was lightweight, and that consumed minimal resources because devices connected via satellite links were very expensive.
There are two versions of the specification: MQTT 3.1.1 and MQTT 5.0.0. Most commercial MQTT brokers now support MQTT 5, but many of the IoT managed cloud services only support MQTT 3.1.1, which used to be the most popular and widely supported version.
It is highly recommended that new IoT deployments use version 5.0.0 (approved in 2018) because its new features focus more on robust systems and cloud native scalability. You can read more of the highlights and detailed differences between both versions on MQTT’s GitHub page.
MQTT is a lightweight client-server protocol that implements the pub/sub message transport model and has been used to connect remote peers/devices in critical IoT applications, Machine-to-Machine (M2M) communication, and many other areas that require a smooth interface for message transport with limited bandwidth but exceptional performance. This is a result of its straightforward design, ease of use, and open specification.
MQTT has been managed by the OASIS technical steering committee since 2014. The committee oversees maintaining, updating, and sustaining the standard, including organizing use-case documents and protecting its intellectual property rights.
It relies on TCP/IP, a reliable and connection-oriented low-level networking stack with a transport layer independent of the data payload structure, enabling an ordered and correct two-way, host-to-host networking model of communication.
The pub/sub pattern also enables messages to be distributed from one application to many different applications at the same time. However, the publisher and the subscriber in this case are usually independent applications (decoupled) and only connected via a broker or a server. For example, the popular pub/sub messaging platform RabbitMQ, makes use of MQTT under the hood.
MQTT has found lots of use cases, which we will explore below.
You can find more details about these on the use case page of MQTT’s docs. One of the most popular open source home automation projects, home-assistant, is based on the MQTT protocol.
The MQTT protocol comprises of the broker, which acts like a central server that channels messages from publisher clients to subscriber clients, and one or more client apps, which can either be subscribers or publishers of messages or data.
Brokers can be said to be like the postman, who ensures messages are delivered to their respective recipients. They should be designed to be highly scalable and secure, since they are the central moot point for MQTT clients.
On a high level, the broker acts as a gateway that routes published messages from publisher apps to the appropriate subscribers. Usually, brokers can be deployed on multiple clusters or instances and made to sit behind a load balancer for better fault tolerance. MQTT clients publish messages to a central broker (usually to a topic) and other clients can subscribe to the same topic on the broker to receive these messages.
Therefore, a client publishes a message to the topic(s), while other clients subscribe to the topic, to indicate they are interested in receiving messages about. The broker has a sort of filtering mechanism, which it uses to control which subscriber it should send messages to. What this means is that the broker checks or filters for a subscriber (or a list of subscribers) on a particular topic or groups of topics and sends messages to them.
In summary, a broker reads, acknowledges, and processes messages (which entails determining the subscribers to topic(s) and sending all appropriate messages to them) sent from a publisher client or app.
Note: MQTT relies on a way of filtering messages, whereby the broker sends messages to subscribers who are interested in getting the message content. Messages usually contain a topic, which is used by the broker to figure out how to route the specific messages to the appropriate, subscribed clients.
Subject-based filtering entails how the clients (publisher and subscriber) interact with the broker via topics, which is part of every message payload.
So far, we have made use of some technical terms that might sound new to some readers. In the next section, we will explore some of these terms and what they mean.
mytopic/homeautomation/closedoor
Now, let us look at how MQTT works on the application level. We are going to use the Node.js client library for MQTT, mqtt.js.
MQTT.js is an open-source JavaScript library for the MQTT protocol, applicable in both Node.js and the browser. Usually, the library can be used for publishing messages and subscribing to topics on an MQTT broker.
Points to note about the MQTT.js library
Note that there are also client libraries available for a vast variety of programming languages and for the main operating systems (Linux, Windows, and macOS).
Client connections are always handled by a broker/server, since MQTT subscribers and publishers are separate, decoupled applications. As we mentioned earlier, both publishers and subscribers are MQTT clients, and therefore need to be connected to the same broker/server.
Clients never connect to each other directly; connection is usually between one client and the broker over TCP/IP. Other MQTT implementations or variants also connect over UDP (MQTT<-SN).
Brokers are responsible for:
Brokers should be easily scaled up and integrated into different backend systems. They can be quite fault-tolerant, as they are the most crucial point of publisher/subscriber communication.
To connect to a broker for the first time, the client sends a CONNECT
message. Once initiated, the broker returns a CONNACK
message and a status code. Another important thing to note is that the broker always keeps the connection alive or open, unless the client sends a disconnect event or their internet connection breaks.
There are some popular, freely-hosted MQTT brokers out there today. Eclipse’s Mosquitto is one of them, and it runs on all major operating systems.
There are also other commercial cloud-based or hosted brokers, like HIVEMQ. They usually come in handy if we do not intend to install and manage our own brokers. You can find a good MQTT broker for quick testing on the MQTT website.
To install MQTT.js, run the following command:
npm install mqtt --save
Note that MQTT.js bundles a command to interact with a broker. To have the MQTT protocol interface available on your system path, we can install it globally:
npm install mqtt -g
At the end of the installation, our package.json
file should look like this:
//package.json { "name": "mqtt-demo", "version": "1.0.0", "description": "A node.js and MQTT demo", "main": "index.js", "scripts": { "start-publisher": "nodemon publisher.js", "start-consumer": "nodemon subscriber.js" }, "keywords": [ "Node.js", "MQTT", "Pub/Sub", "IoT", "message", "transport" ], "author": "Alexander Nnakwue", "license": "MIT", "dependencies": { "dotenv": "^10.0.0", "mqtt": "^4.3.2" }, "devDependencies": { "nodemon": "^2.0.15" } }
To test the program, you can run the publisher on one terminal window and run the subscriber on another terminal window.
Now, let us create an MQTT client that publishes messages. To do so, we can import the MQTT.js library and use the connect method.
const mqtt = require('mqtt') require('dotenv').config() const clientId = 'mqttjs_' + Math.random().toString(8).substr(2, 4) const client = mqtt.connect(process.env.BROKER_URL, {clientId: clientId});
Notice that we have added the BROKER_URL
to our environment file. As we can see, the connect
method accepts a given URL (broker server URL) and an optional server options object. The accepted protocols can either be MQTT
, ws
, wss
, tcp
, tls
and so on. The connect method returns a connected client.
To attempt a reconnection when an MQTT client connection is broken, we can set the reconnectPeriod
option (an interval between two reconnection times) to be greater than zero. The default value is 1
second, which means that after disconnecting it will try to reopen the connection again almost immediately.
const client = mqtt.connect(process.env.BROKER_URL, {clientId: clientId, clean: false, reconnectPeriod: 1});
If we set the value of the reconnectPeriod
client option to 0
, the reconnection will be disabled and terminated when the connection is dropped.
When the resubscribe option is set to its default value (true
), clients can automatically reconnect and resubscribe to a previously-subscribed topic when connections are broken. Most especially for self-hosted brokers, we might want to authenticate with a username and a password. More details of the server options object can be found on the MQTT GitHub.
Once connected to a broker, an MQTT client can send messages almost immediately. The publishing event takes a message payload and a topic name, which the broker can use to identify the subscribed parties. Also, there is an option callback that checks for an error or when the message packet has been transmitted.
The published message type or packet sent has the following attributes:
topicName
dupFlag
qos
payload
packetId
or messageId
retainFlag
Command as shown below.
{ cmd: 'publish', topic: 'test/connection', payload: '{"1":"Hello world","2":"Welcome to the test connection"}', qos: 1, retain: true, messageId: 12041, dup: false } MQTT publish packet
When a client sends a message to a broker, the broker processes the message based on some criteria set by a developer. This should include the QoS level, which determines what kind of guarantee a message has for reaching the intended recipients and ensures message delivery guarantees.
The processing stage usually entails reading, acknowledging, and identifying the clients who have subscribed to the topics. The last step is to send messages to the subscribed clients.
Here is the complete code for the publisher.js
file, including some of the public methods/APIs:
//publisher.js const mqtt = require('mqtt') require('dotenv').config() //the client id is used by the MQTT broker to keep track of clients and and their // state const clientId = 'mqttjs_' + Math.random().toString(8).substr(2, 4) const client = mqtt.connect(process.env.BROKER_URL, {clientId: clientId, clean: false}); // console.log(process.env.BROKER_URL, 'client', clientId) const topicName = 'test/connection' client.on("connect",function(connack){ console.log("client connected", connack); // on client connection publish messages to the topic on the server/broker const payload = {1: "Hello world", 2: "Welcome to the test connection"} client.publish(topicName, JSON.stringify(payload), {qos: 1, retain: true}, (PacketCallback, err) => { if(err) { console.log(err, 'MQTT publish packet') } }) //assuming messages comes in every 3 seconds to our server and we need to publish or process these messages setInterval(() => console.log("Message published"), 3000); }) client.on("error", function(err) { console.log("Error: " + err) if(err.code == "ENOTFOUND") { console.log("Network error, make sure you have an active internet connection") } }) client.on("close", function() { console.log("Connection closed by client") }) client.on("reconnect", function() { console.log("Client trying a reconnection") }) client.on("offline", function() { console.log("Client is currently offline") })
Next, we can go ahead and implement the subscribing client, which consumes the messages on the topics.
To receive messages about the topics we are interested in, the client calls the subscribe
event to the broker. The subscribed message usually contains a message packet payload, as shown below.
//stdout Packet { cmd: 'publish', retain: true, qos: 0, dup: false, length: 73, topic: 'test/connection', payload: <Buffer 7b 22 31 22 3a 22 48 65 6c 6c 6f 20 77 6f 72 6c 64 22 2c 22 32 22 3a 22 57 65 6c 63 6f 6d 65 20 74 6f 20 74 68 65 20 74 65 73 74 20 63 6f 6e 6e 65 63 ... 6 more bytes> } {"1":"Hello world","2":"Welcome to the test connection"}``` // [ { topic: 'test/connection', qos: 0 } ] granted
Instead of having topic names as regular delimited strings, we can also store topics as wildcards so that subscribers can easily subscribe to topic patterns, rather than a single topic at a time. The publisher and subscriber clients need to be aware of the topic names for patterns beforehand to do this.
Note that the subscribing clients need to know the structure of the data they’ll receive beforehand, so they can appropriately handle the data. The publisher sends messages to a particular topic in the broker in a particular format, and the intended subscriber of that message needs to know how that data is structured, so they are able to handle it correctly without breaking the application.
The complete code for the subscriber client is is shown in the file below.
// subscriber.js const mqtt = require('mqtt') const client = mqtt.connect("mqtt://test.mosquitto.org") const topicName = 'test/connection' // connect to same client and subscribe to same topic name client.on('connect', () => { // can also accept objects in the form {'topic': qos} client.subscribe(topicName, (err, granted) => { if(err) { console.log(err, 'err'); } console.log(granted, 'granted') }) }) // on receive message event, log the message to the console client.on('message', (topic, message, packet) => { console.log(packet, packet.payload.toString()); if(topic === topicName) { console.log(JSON.parse(message)); } }) client.on("packetsend", (packet) => { console.log(packet, 'packet2'); })
Some features of MQTT are described below.
A retained message is an MQTT message with the retained flag/option set to true
. By default, when a broker/server receives messages for a topic that doesn’t have subscribers, the messages are discarded. However, MQTT has a mechanism of retaining these messages by setting a flag, which tells the publisher to keep the message. Note that the broker only stores one retained message per topic.
MQTT supports various authentication methods and data security mechanisms, including TLS and OAuth, which are usually configured on the MQTT broker. Therefore, this means that clients who implement these servers need to comply with the mechanisms as defined.
By default, MQTT supports a reconnection mechanism that sets up persistent connections in areas of low connectivity. This is important for storing messages, but, unlike traditional queuing systems, brokers do not exclusively store messages. MQTT stores messages by making sure client sessions are persistent and that the QoS levels are greater than 0
.
To handle the challenges typical in pub/sub systems, MQTT has implemented three Quality of Service (QoS) levels. These three levels include 0, 1, and 2. and they determine what kind of guarantee a message has for reaching the intended recipient (clients or brokers alike).
To support reliable message delivery, the protocol supports three distinct types of quality of services messages:
For storing messages, clients must have subscribed to a topic with a Quality of Service greater than 0
.
MQTT is data-agnostic, and the use case of the client determines how the payload is structured. It is therefore possible to send any kind of message, including images, encoded text, encrypted data and so on.
Note: The default unencrypted MQTT port is 1883
. The encrypted TCP/IP port 8883
is also supported for using MQTT over SSL.
MQTT offers a two-way pub/sub messaging model suitable for areas where network bandwidth is limited. Services are independent of our major applications since they are decoupled, and brokers or servers can be individually scaled.
Here is a list of open source projects leveraging on the MQTT protocol on GitHub. Details of our demo application can be found on GitHub, and you can also find details of the MQTT standard on GitHub.
Deploying a Node-based web app or website is the easy part. Making sure your Node instance continues to serve resources to your app is where things get tougher. If you’re interested in ensuring requests to the backend or third-party services are successful, try LogRocket.
LogRocket is like a DVR for web and mobile apps, recording literally everything that happens while a user interacts with your app. Instead of guessing why problems happen, you can aggregate and report on problematic network requests to quickly understand the root cause.
LogRocket instruments your app to record baseline performance timings such as page load time, time to first byte, slow network requests, and also logs Redux, NgRx, and Vuex actions/state. Start monitoring for free.
Would you be interested in joining LogRocket's developer community?
Join LogRocket’s Content Advisory Board. You’ll help inform the type of content we create and get access to exclusive meetups, social accreditation, and swag.
Sign up nowBuild scalable admin dashboards with Filament and Laravel using Form Builder, Notifications, and Actions for clean, interactive panels.
Break down the parts of a URL and explore APIs for working with them in JavaScript, parsing them, building query strings, checking their validity, etc.
In this guide, explore lazy loading and error loading as two techniques for fetching data in React apps.
Deno is a popular JavaScript runtime, and it recently launched version 2.0 with several new features, bug fixes, and improvements […]