Firebase Realtime Database is one of the most beginner-friendly cloud backends for IoT projects: no server to manage, a simple JSON tree you read and write with REST calls or the Firebase ESP32 client library, and instant push notifications to all connected clients when data changes. In this tutorial you'll wire a DHT22 temperature/humidity sensor to an ESP32, push readings to Firebase every 30 seconds, listen for a remote relay command from a web dashboard, and build the dashboard with plain HTML and the Firebase JavaScript SDK — no frameworks required.

// What you'll build: ESP32 reads DHT22 temperature and humidity, writes them to /devices/sensor_01 in Firebase every 30 s. A web dashboard reads those values in real time, plots a 24-hour chart, and lets you toggle a relay via /commands/relay. The ESP32 streams the commands node and toggles a GPIO instantly when the value changes.
FULL BUILD DEMO ON YOUTUBE

Watch the complete build on the ESPSTACK YouTube channel →

// AD SLOT — IN-CONTENT RESPONSIVE

Prerequisites

ComponentQuantityNotes
ESP32 DevKit1With WiFi; any variant
DHT22 (AM2302) sensor1Or DHT11; adjust library define accordingly
10 kΩ pull-up resistor1Between DHT DATA and 3.3 V
Relay module (optional)1For remote command demo; 5 V active-low relay

Libraries: Firebase ESP32 Client by Mobizt (Library Manager), DHT sensor library by Adafruit.

Step 1 — Firebase Project Setup

  1. Create a Firebase project at console.firebase.google.com.
  2. Enable Realtime Database — choose a region, start in test mode (then add rules before going public).
  3. Copy the Database URL (looks like https://yourproject-default-rtdb.firebaseio.com/).
  4. Under Project Settings → Service accounts generate a legacy server key, or use the Web API key from the General tab for the client library.
// Security: Test-mode Firebase rules allow public read/write. Before deploying to production, restrict access: ".read": "auth != null". Never ship the database secret in public firmware — use Firebase Auth token exchange for device auth.

Step 2 — Push Sensor Data from ESP32

#include <Arduino.h>
#include <WiFi.h>
#include <Firebase_ESP_Client.h>
#include <DHT.h>
#include "addons/TokenHelper.h"
#include "addons/RTDBHelper.h"

#define WIFI_SSID    "YourSSID"
#define WIFI_PASS    "YourPassword"
#define API_KEY      "YOUR_FIREBASE_API_KEY"
#define DATABASE_URL "https://yourproject-default-rtdb.firebaseio.com/"

#define DHT_PIN  4
#define DHT_TYPE DHT22
#define RELAY_PIN 26

DHT    dht(DHT_PIN, DHT_TYPE);
FirebaseData fbData;
FirebaseAuth auth;
FirebaseConfig config;

bool tokenOk = false;

void tokenStatusCb(TokenInfo info) {
  if (info.status == token_status_ready) tokenOk = true;
}

void setup() {
  Serial.begin(115200);
  dht.begin();
  pinMode(RELAY_PIN, OUTPUT);
  digitalWrite(RELAY_PIN, HIGH); // active-low relay: start OFF

  WiFi.begin(WIFI_SSID, WIFI_PASS);
  while (WiFi.status() != WL_CONNECTED) { delay(500); Serial.print("."); }
  Serial.printf("\nConnected: %s\n", WiFi.localIP().toString().c_str());

  config.api_key      = API_KEY;
  config.database_url = DATABASE_URL;
  config.token_status_callback = tokenStatusCb;

  Firebase.begin(&config, &auth);
  Firebase.reconnectWiFi(true);
}

uint32_t lastPush = 0;

void pushSensorData() {
  float temp = dht.readTemperature();
  float hum  = dht.readHumidity();
  if (isnan(temp) || isnan(hum)) { Serial.println("DHT read error"); return; }

  FirebaseJson json;
  json.set("temp", temp);
  json.set("hum",  hum);
  json.set("ts",   (long)time(nullptr));

  if (Firebase.RTDB.setJSON(&fbData, "/devices/sensor_01", &json)) {
    Serial.printf("Pushed: %.1f°C  %.0f%%\n", temp, hum);
  } else {
    Serial.printf("Push error: %s\n", fbData.errorReason().c_str());
  }
}

void checkCommands() {
  String relay;
  if (Firebase.RTDB.getString(&fbData, "/commands/relay", &relay)) {
    digitalWrite(RELAY_PIN, relay == "ON" ? LOW : HIGH);
  }
}

void loop() {
  if (!Firebase.ready() || !tokenOk) return;

  if (millis() - lastPush > 30000) {
    lastPush = millis();
    pushSensorData();
    checkCommands();
  }
}
// AD SLOT — IN-CONTENT RESPONSIVE

Step 3 — Real-time Stream for Instant Commands

Polling /commands/relay every 30 seconds is fine for slow telemetry but too slow for a relay toggle. Use Firebase's streaming API to receive push notifications the moment the value changes.

FirebaseData streamData;

void streamCb(FirebaseStream data) {
  if (data.dataTypeEnum() == fb_esp_rtdb_data_type_string) {
    String val = data.stringData();
    digitalWrite(RELAY_PIN, val == "ON" ? LOW : HIGH);
    Serial.printf("Relay command: %s\n", val.c_str());
  }
}

void streamTimeout(bool timeout) {
  if (timeout) Serial.println("Stream timeout — reconnecting");
}

// Call once after Firebase.begin()
void startStream() {
  if (!Firebase.RTDB.beginStream(&streamData, "/commands")) {
    Serial.printf("Stream error: %s\n", streamData.errorReason().c_str());
  }
  Firebase.RTDB.setStreamCallback(&streamData, streamCb, streamTimeout);
}

Step 4 — Web Dashboard (HTML + Firebase JS SDK)

<!-- dashboard.html -->
<script type="module">
import { initializeApp }     from "https://www.gstatic.com/firebasejs/10.8.0/firebase-app.js";
import { getDatabase, ref, onValue, set }
  from "https://www.gstatic.com/firebasejs/10.8.0/firebase-database.js";

const app = initializeApp({ apiKey:"...", databaseURL:"..." });
const db  = getDatabase(app);

// Live sensor display
onValue(ref(db, "/devices/sensor_01"), snap => {
  const d = snap.val();
  document.getElementById("temp").textContent = d.temp.toFixed(1) + " °C";
  document.getElementById("hum").textContent  = d.hum.toFixed(0) + " %";
});

// Relay toggle button
document.getElementById("relay-btn").onclick = async () => {
  const snap = await get(ref(db, "/commands/relay"));
  const current = snap.val();
  await set(ref(db, "/commands/relay"), current === "ON" ? "OFF" : "ON");
};
</script>

Troubleshooting

SymptomLikely CauseFix
Firebase.ready() always falseAPI key incorrect or no internetVerify API key from Firebase console; check WiFi connection and DNS
Push succeeds but data not visible in consoleWrong database URL (missing trailing /)URL must be exact; test with a REST GET: curl [url]/.json
DHT returns NaNNo pull-up resistor or bad pinAdd 10 kΩ between DATA and 3.3 V; avoid GPIO 0/2/15 which affect boot
Stream disconnects frequentlyPoor WiFi or Firebase 30-min token refreshCall Firebase.reconnectWiFi(true); keep token auto-refresh enabled
ESP32 heap exhausted after hours of uptimeFirebaseData accumulates response stringsCall fbData.clear() after each operation or use separate FirebaseData per operation

Next Steps

  • Add Firebase Authentication so only logged-in users can read/write the database — replace test-mode rules with user-scoped rules.
  • Use Firebase Cloud Functions to trigger an email or push notification when temperature exceeds a threshold.
  • Store historical readings in Firebase's Firestore (not RTDB) with a TTL Cloud Function to automatically prune data older than 7 days.
  • Add a deep-sleep cycle — push data every 10 minutes and sleep between readings to run the ESP32 from a LiPo battery for weeks.