• Tutorials
  • How to Forward MQTT Messages to Your PHP Backend Using Node.js

    This example demonstrates how to create a Node.js "forwarder" that listens to an MQTT broker, expects JSON payloads on a topic, and forwards them via HTTP(S) to a PHP backend endpoint. The solution consists of three parts: environment configuration, Node.js dependencies, and the main forwarder script.

    #Requirements

    Familiarity with Node.js and MQTT.

    #What This Example Does — A Quick Overview

    This example shows a small Node.js "forwarder" that listens to an MQTT broker, expects JSON payloads on a topic, and forwards them via HTTP(S) to a PHP backend endpoint. The repository files demonstrate the configuration (.env-style snippet), the Node.js runtime dependencies (package.json), and the main script that connects to MQTT, parses incoming messages as JSON, and posts them to the configured web API using Axios.

    #The Configuration (.env)

     1# if env is local it will allow self-signed certificates
     2ENV=local
     3
     4# domain or ip address
     5WEBSITE=192.168.1.102
     6
     7# port like 80 or 443 or something else
     8PORT=8003
     9
    10# protocol either "http" or "https"
    11PROTOCOL=http
    

    The .env snippet contains four environment variables:

    • ENV: set to "local" in the example. The script uses this to allow self-signed TLS certificates during local testing.
    • WEBSITE: the domain or IP address of your PHP backend.
    • PORT: the port the backend listens on.
    • PROTOCOL: either "http" or "https".

    These variables are combined in the script to construct the destination URL for the forwarded HTTP POST. Using env variables keeps credentials and environment-specific settings out of source code and makes the forwarder easy to deploy across dev/staging/production by changing the environment only.

    #The Package Manifest (package.json)

     1{
     2  "name": "mqttforwarder",
     3  "version": "0.1.0",
     4  "description": "mqtt message forwarder",
     5  "main": "forwarder.js",
     6  "repository": {
     7    "type": "git",
     8    "url": "https://gitlab.com/example/example"
     9  },
    10  "scripts": {
    11    "test": "echo \"Error: no test specified\" && exit 1"
    12  },
    13  "keywords": [],
    14  "author": "",
    15  "license": "ISC",
    16  "dependencies": {
    17    "axios": "^1.7.2",
    18    "dotenv": "^16.0.1",
    19    "mqtt": "^4.1.0"
    20  }
    21}
    

    The package.json shows the forwarder's dependencies:

    • mqtt: the MQTT client used to connect to and subscribe to topics on the broker.
    • axios: the HTTP client used to POST parsed message payloads to the PHP endpoint.
    • dotenv: loads the .env file into process.env for local development.

    This file allows easy installation with npm install and documents the runtime requirements.

    #The Node.js Forwarder Script — What Happens Step by Step

     1require("dotenv").config(); // in order to access env variables
     2const mqtt = require("mqtt"); // for connecting and subscribing to an MQTT broker
     3const axios = require("axios"); // for making ajax requests
     4
     5// connect to Broker using a url and port
     6const client = mqtt.connect("mqtt://example.com:1883");
     7
     8client.on("connect", () => {
     9	// subscribe to a topic
    10	// # just means listen to all subtopics too
    11	// qos - is the MQTT protocol quality of service option
    12	// we don't need here that's why it is 0.
    13	client.subscribe("topic/#", { qos: 0 });
    14});
    15
    16client.on("message", function (topic, message) {
    17    console.log("this message :", topic + "/" + message.toString());
    18
    19    try {
    20        let path = "/process-endpoint";
    21        let env = process.env.ENV;
    22        let messageString = message.toString();
    23		let data = JSON.parse(messageString); // expecting valid JSON message
    24
    25        if (env === "local") {
    26			// if your testing locally and your PHP backend
    27			// is using a self-signed certificate
    28			// this will make sure that data is still send
    29            process.env["NODE_TLS_REJECT_UNAUTHORIZED"] = 0;
    30        }
    31
    32        const URL = process.env.PROTOCOL + "://" + process.env.WEBSITE + ':' + process.env.PORT + path;
    33
    34        axios
    35            .post(URL, data, {
    36                responseType: "json",
    37                transformResponse: (body) => body,
    38                headers: {
    39                    "Content-Type": "application/json",
    40                },
    41            })
    42            .then((response) => {
    43                console.log(response?.data);
    44            })
    45            .catch((error) => {
    46                console.log(error?.response?.data);
    47            });
    48    } catch (error) {
    49        console.log(error);
    50    }
    51});
    

    The forwarder script works in the following steps:

    1. Environment Setup: dotenv loads environment variables so the script can build the target URL.
    2. MQTT Connection: The script uses mqtt.connect("mqtt://example.com:1883") to open an MQTT connection. Replace the broker URL and port with your broker's address.
    3. Topic Subscription: On successful connection, it subscribes to topic/#. The # wildcard means "all subtopics under topic/" so the forwarder will receive messages published to topic/foo, topic/bar/baz, etc.
    4. Message Processing: When a message arrives the client.on("message", (topic, message) => { ... }) handler runs:
      • The code calls message.toString() and attempts JSON.parse() to convert the payload into an object. That means the publisher must send valid JSON. A malformed payload will throw and get logged.
      • If ENV === "local", the script sets NODE_TLS_REJECT_UNAUTHORIZED = 0 to allow self-signed certificates for local testing only. This is convenient for development but must never be used in production.
      • The target URL is built from PROTOCOL, WEBSITE, PORT, and a fixed path (/process-endpoint).
      • Axios posts the parsed data as JSON to the PHP endpoint. The code logs the response data on success or logs the error response data on failure.

    #Message Flow and Contract

    PublisherMQTT brokerNode forwarder subscribes to topicforwarder parses JSONforwarder POSTs JSON to PHP backend.

    The unspoken contract here: messages are expected to be valid JSON and shaped in the way the PHP endpoint expects. You should document the expected JSON schema (keys, types) so both producers and the PHP consumer agree.

    #Error Handling and Reliability Notes

    • Current example does basic try/catch for parse errors and logs Axios responses/errors. It does not implement retries or persistent queuing.
    • If the PHP endpoint is temporarily unavailable, the forwarder logs the error but drops the message. For better reliability consider:
      • Retry with exponential backoff for transient HTTP errors.
      • Use persistent queueing (e.g., local disk queue, Redis, RabbitMQ) to avoid data loss on restart.
      • For high-throughput systems, consider batching messages or using a message queue to decouple ingestion from forwarding.
    • MQTT QoS: the script subscribes with QoS 0. If message delivery guarantees are important, increase the QoS (publisher and subscriber must support it) to get at-least-once (QoS 1) or exactly-once (QoS 2) delivery characteristics—though QoS 2 is not commonly used in many brokers.

    #Security Considerations

    • Never set NODE_TLS_REJECT_UNAUTHORIZED = 0 in production — it disables TLS verification and makes MITM attacks possible.
    • Protect the PHP endpoint:
      • Require authentication (API key, OAuth token, mutual TLS).
      • Validate and sanitize incoming JSON; never trust the payload.
      • Rate-limit and log to detect abuse.
    • If possible, use TLS (https) between the forwarder and backend and authenticate the forwarder (client certs or an API key).
    • Consider authenticating to the MQTT broker as well (username/password, TLS).

    #Deployment and Scaling Hints

    • Run the forwarder as a managed service (systemd, Docker container, or a process manager like PM2). Ensure logs go to a centralized system for observability.
    • For scale, run multiple forwarder instances with distinct client IDs (or share subscription load depending on broker features). Ensure idempotency on the PHP side because multiple deliveries could occur (MQTT QoS >0 + network retries).
    • Monitor consumer latency and HTTP response codes. Add health checks and restart policies.

    #Small Suggested Improvements

    • Add validation for incoming JSON (e.g., use Joi or a lightweight schema validator) before forwarding.
    • Add retries with jitter for transient network errors when calling the HTTP API.
    • Add structured logging (timestamp, topic, message-id if present) for easier troubleshooting.
    • Make the destination path configurable via env instead of hardcoding /process-endpoint.
    • Consider publishing an acknowledgement message back to an MQTT topic after successful forwarding if you need end-to-end visibility.
    profile image of Petar Vasilev

    Petar Vasilev

    Petar is a web developer at Mitkov Systems GmbH. He is fascinated with the web. Works with Laravel and its ecosystem. Loves learning new stuff.

    More posts from Petar Vasilev