Node-RED Flows and Dashboard for ESP32 MQTT Sensors and Relays
Node-RED is a flow-based programming tool that runs on Node.js. Instead of writing a full server application, you drag nodes onto a canvas and wire them together: an MQTT input, a bit of JavaScript to reshape the message, a gauge on a web dashboard. It runs well on a Raspberry Pi next to Mosquitto, which together make a complete self-hosted IoT backend.
In this tutorial you will connect Node-RED to the Mosquitto broker, receive the JSON telemetry from the ESP32 MQTT publisher tutorial, show it on gauges and a chart, add a switch that sends relay commands back to the ESP32, and raise an alert when the temperature crosses a limit.
Installing Node-RED
Install a current LTS release of Node.js first. Then install Node-RED globally with npm and start it. On a Raspberry Pi, the Node-RED project also provides an install script that sets it up as a system service; see the Getting Started section of nodered.org.
# Linux / Raspberry Pi (omit sudo on Windows)
sudo npm install -g --unsafe-perm node-red
# start it; the editor opens at http://<host-ip>:1880
node-red
For the dashboard, open the editor menu, choose Manage palette > Install, search for @flowfuse/node-red-dashboard and install it. This is Dashboard 2.0, the actively maintained successor to the older node-red-dashboard package, which is deprecated. Its widgets appear in the palette under the dashboard 2 category.
The Flow We Will Build
[mqtt in: home/node1/telemetry] -> [function: split] --out1--> [ui-gauge Temperature]
| -> [ui-chart Temp history]
+--out2--> [ui-gauge Humidity]
+--out3--> [switch > 35] -> [delay 1 msg/5min] -> [ui-notification]
[ui-switch Fan] -> [mqtt out: home/relay1/set]
[mqtt in: home/relay1/state] -> [ui-switch Fan] (keeps switch in sync)| Node | Palette category | Key settings |
|---|---|---|
| mqtt in | network | Server: broker IP, port 1883, username and password; Topic: home/node1/telemetry; Output: a parsed JSON object |
| function | function | Outputs: 3; code shown below |
| ui-gauge | dashboard 2 | Range 0 to 50 for temperature, 0 to 100 for humidity; units °C and % |
| ui-chart | dashboard 2 | Type: line; X-axis: timestamp; keep data for a time window you choose |
| switch | function | Property msg.payload, rule > 35 |
| delay | function | Action: rate limit, 1 msg per 5 minutes, drop intermediate messages |
| ui-notification | dashboard 2 | Shows a pop-up on the dashboard page |
| ui-switch | dashboard 2 | On payload string ON, off payload string OFF |
| mqtt out | network | Topic: home/relay1/set, QoS 1, retain false |
Function Node Code
The MQTT input already parses the JSON, so msg.payload is an object like {temp_c: 28.4, hum_pct: 61}. A function node with three outputs returns an array; each element goes to the matching output, and null sends nothing on that output.
// msg.payload = { temp_c: 28.4, hum_pct: 61.2, rssi: -58 }
const p = msg.payload;
// Ignore anything that is not a valid reading
if (typeof p !== 'object' || typeof p.temp_c !== 'number') {
node.warn('Bad payload: ' + JSON.stringify(p));
return null;
}
// Output 1: temperature for the gauge and the chart
const temp = { payload: p.temp_c, topic: 'Temperature' };
// Output 2: humidity for its gauge
const hum = { payload: p.hum_pct, topic: 'Humidity' };
// Output 3: raw value for the alert switch node
const alert = { payload: p.temp_c,
topic: 'High temperature: ' + p.temp_c + ' °C' };
// Remember the last value so other flows can use it
flow.set('lastTemp', p.temp_c);
node.status({ fill: 'green', shape: 'dot', text: p.temp_c + ' °C' });
return [temp, hum, alert];
The topic of each message becomes the series name in the chart, so you can feed several sensors into one chart and get a separate line for each. node.status() shows a small label under the node in the editor, which is very handy for debugging without opening the debug panel.
Before the notification node, add a change node that sets msg.payload to msg.topic, so the pop-up shows the readable text rather than just the number.
Sending Commands Back to the ESP32
The ui-switch node emits ON or OFF when the user taps it, and the mqtt out node publishes that to home/relay1/set. The ESP32 subscribes to that topic, switches the relay, and publishes its real state to home/relay1/state as a retained message. Wiring that state topic back into the switch's input keeps the dashboard honest: if the ESP32 is offline, the switch does not falsely show ON.
In the ui-switch properties, disable the option that passes input messages straight through to the output. Otherwise every state message from the ESP32 would be republished as a command, creating a loop.
Deploy and view
- Create a ui-base, a ui-page (path
/home) and a ui-group named "Living Room" when the first widget asks for a group. - Click Deploy. Both MQTT nodes should show a green "connected" status.
- Open
http://<host-ip>:1880/dashboardon a PC or phone on the same network. - Use a debug node on any wire while building; its output appears in the debug sidebar.
Troubleshooting
- MQTT node stays "connecting": wrong broker IP or credentials. Remember that inside Node-RED on the same Pi,
localhostworks as the broker address. - Gauge shows NaN or nothing: the payload is still a string. Set the mqtt in output to "a parsed JSON object" or add a json node.
- Chart stays empty: the payload is not a number, or the X-axis type does not match the data. Keep the X-axis as timestamp and send numeric payloads only.
- Alert floods the screen: the rate-limit delay node is missing or set to queue rather than drop intermediate messages.
- Editor is open to the whole network: enable
adminAuthin Node-RED'ssettings.jsand set a password before exposing port 1880 beyond your LAN.
Summary
Node-RED connects to Mosquitto with an mqtt in node, reshapes data with short JavaScript function nodes, and presents it on a Dashboard 2.0 page with gauges, charts, switches and notifications. With state fed back from the device and rate-limited alerts, you have a practical, self-hosted control panel for any number of ESP32 nodes.