Zyron
ZYRON
All Posts
ESP32MQTTIoTDashboardFirmwareNext.js

How to Build a Real-Time Sensor Dashboard with ESP32 and a Web Interface

9 min read10 March 2026Zyron Engineering

One of the most common IoT requirements is deceptively simple to state and surprisingly easy to get wrong: read a sensor, show the value on a web page, keep it up to date. The naive approach — poll an HTTP endpoint every few seconds — works until it doesn't. This walkthrough covers the right architecture for a real-time sensor dashboard: MQTT from the ESP32, a persistent backend, and WebSockets to the browser.

The architecture

Three layers, three technologies. The ESP32 reads your sensor and publishes to an MQTT broker on a configurable interval — typically every 1–5 seconds for most monitoring use cases. A backend service (Python or .NET) subscribes to the broker, validates incoming payloads, writes to a time-series-friendly database table, and exposes a WebSocket endpoint. The frontend connects to that WebSocket and updates the UI in real time without polling.

ESP32 firmware: reading and publishing

Start with the Arduino framework and the PubSubClient library for MQTT. After WiFi connects, establish your MQTT connection with a client ID, username, and password. In your main loop, read the sensor — a DHT22 for humidity and temperature, a current transformer for power measurement, or whatever your application needs — format the reading as a JSON payload, and publish to a topic like sensors/node01/temperature.

Two things matter here that most tutorials skip: reconnect logic and payload validation. Your broker connection will drop. Build a reconnect function that runs before every publish attempt. And never publish a NaN — check sensor readings before formatting them into JSON. A corrupted payload is worse than a missing one because it silently corrupts your historical data.

MQTT broker: Mosquitto on Docker

Run Mosquitto in a Docker container. Two minutes of setup, persistent config, and it handles thousands of messages per second on a Raspberry Pi. Configure authentication — even on a local network, unauthenticated MQTT is a bad habit. Create a password file, reference it in mosquitto.conf, and set allow_anonymous false. If your ESP32 nodes are on the same network as the broker, plain MQTT over port 1883 is fine. If they're crossing the internet, use MQTT over TLS on port 8883.

Backend: subscribing and storing

A Python script using the paho-mqtt library is the fastest path to a working subscriber. Connect to the broker, subscribe to your sensor topic pattern (sensors/# to catch all nodes), and in your on_message callback parse the JSON payload and insert into PostgreSQL. Use a table with columns: node_id, metric, value, recorded_at. Index on (node_id, recorded_at) and you'll have fast queries for both live readings and historical ranges.

For the WebSocket layer, FastAPI with websockets is a clean choice. On each new MQTT message, push the parsed reading to all connected WebSocket clients. Keep a set of active connections and handle disconnects gracefully — a disconnected client will throw an exception on send.

Frontend: live updates without polling

In your React or Next.js frontend, open a WebSocket connection on component mount and close it on unmount. On each message event, parse the JSON and update your state. A simple useState holding the latest reading per node is enough for a live display. For sparklines and trend charts, maintain a rolling buffer of the last N readings in state.

If your backend goes down and the WebSocket disconnects, implement exponential backoff reconnection in the client — start at 1 second, double each attempt, cap at 30 seconds. Show a subtle connection status indicator so operators know when they're looking at stale data.

OTA updates: deploying firmware without touching the hardware

Once you have multiple nodes deployed, physical firmware updates become impractical. The ESP32 supports OTA (over-the-air) updates natively via the ArduinoOTA library or the ESP-IDF OTA partition scheme. For a fleet, serve the firmware binary from your backend and have each ESP32 check a version endpoint on boot. If the version differs, download and apply the update. Build a rollback mechanism: if the new firmware fails to connect within 60 seconds of first boot, roll back to the previous partition automatically. A firmware update that bricks a remote sensor node is a field trip you don't want to make.

What to build next

This stack — ESP32 + MQTT + PostgreSQL + WebSockets — scales to dozens of nodes without architectural changes. Add configurable alerting by evaluating each incoming reading against stored thresholds and firing a notification (email, SMS, webhook) when a value goes out of range. Add historical trend charts by querying your PostgreSQL table with a time range. Add multi-tenancy by namespacing your MQTT topics and adding a tenant_id column to your readings table. The foundation is the same for a single hobby sensor or a 50-node industrial monitoring deployment.

Need this built, not just explained?

We build production systems around exactly this kind of engineering. Tell us what you're working on.

Start a conversation