What You Will Build
A local web page hosted on the ESP32 itself that lets you control an LED from any browser on your network. By the end of this guide you will have a working URL like http://192.168.1.120/ that shows LED state, toggles it on/off, and returns live status as JSON.
Hardware Setup
- LED + 220 Ω resistor connected to GPIO2 (built-in LED on most DevKit boards)
- ESP32 DevKit connected via USB
Minimal Web Server
#include <WiFi.h>
#include <WebServer.h>
const char* ssid = "YourSSID";
const char* password = "YourPassword";
const int LED_PIN = 2;
WebServer server(80);
bool ledState = false;
String buildPage() {
String html = "
Setting Up mDNS (Access by Name)
Instead of remembering an IP address, configure mDNS so the device is reachable at a friendly hostname:
#include <ESPmDNS.h>
// In setup(), after WiFi connects:
if (MDNS.begin("esp32-control")) {
MDNS.addService("http", "tcp", 80);
Serial.println("mDNS: http://esp32-control.local/");
}
Now any device on the same LAN can browse to http://esp32-control.local/ without knowing the IP.
Real-Time Updates with AJAX Polling
Refresh sensor data without a full page reload by adding a small JavaScript fetch loop to your HTML:
<div id="status">Loading…</div>
<script>
setInterval(async () => {
const r = await fetch('/status');
const d = await r.json();
document.getElementById('status').textContent =
'LED: ' + (d.led ? 'ON' : 'OFF') + ' | Uptime: ' + d.uptime + 's';
}, 2000);
</script>
Serving Multiple GPIO Pins
void handlePin() {
if (server.hasArg("pin") && server.hasArg("state")) {
int pin = server.arg("pin").toInt();
bool state = server.arg("state") == "1";
if (pin >= 0 && pin <= 39) {
pinMode(pin, OUTPUT);
digitalWrite(pin, state ? HIGH : LOW);
server.send(200, "application/json", "{\"ok\":true}");
} else {
server.send(400, "application/json", "{\"error\":\"invalid pin\"}");
}
}
}
// Register: server.on("/pin", HTTP_GET, handlePin);
// Call: GET /pin?pin=4&state=1
Receiving POST Data from a Form
void handleForm() {
if (server.method() == HTTP_POST) {
String name = server.arg("name");
String value = server.arg("value");
Serial.printf("Received: %s = %s\n", name.c_str(), value.c_str());
server.send(200, "text/plain", "Saved: " + name + " = " + value);
} else {
server.send(405, "text/plain", "Method not allowed");
}
}
Performance Tips
- Call
server.handleClient()every loop iteration — never put long blocking code inloop()when a web server is running. - Use
PROGMEMfor large HTML — store the page template in flash, not SRAM, withconst char html[] PROGMEM = "...". - Compress static files — serve pre-gzipped CSS/JS and set the
Content-Encoding: gzipheader for up to 70% size reduction. - For concurrent clients — switch to
ESPAsyncWebServerwhich handles multiple connections without blocking.