ESP32/Web server/51. Hello world from the board
Your chip
Your language
Web server · 51 of 81

Hello world from the board

Thirty lines and the board has a URL of its own. The idea worth taking from them is that a web server is a table of paths, and one line in loop() is the difference between answering and hanging.

/esp32/web-server-hello-world · arduino · S3

Ask for a path the board knows, then one it does not, then take handleClient() away.

GET /
handleClient() in loop()
The browser asks for
Inside loop()
Status
200
Content-Type
text/html
Browser shows
the page
The path matched a row, so that row's handler ran. server.on registered it and did nothing else; the call in loop() is what found the waiting request and called it. Two separate things, and only one of them is in setup().

A server is a table and a pump

Two things make it work, and beginners usually only notice the first.

  • server.on(path, method, handler) adds a row to a table. Nothing runs.
  • server.handleClient() looks for a waiting request, finds the matching row, and calls it. Take it out of loop() and the board still accepts the connection — it just never says anything, so the browser spins.

That is why a blocked loop() looks identical to a crashed board from the outside.

The address is private

WiFi.localIP() prints something like 192.168.1.47. That works from a phone on the same Wi-Fi, and from nowhere else. It is not a bug and there is no setting to change it. If you want the readings from outside the house, push them out to a broker rather than letting the world in — MQTT and a broker is that page.

The address also moves. The router hands it out on a lease, so it changes after a power cut. mDNS gives the board a name that does not.

Do not build pages out of strings

A one-line <h1> is fine. The moment the page has a stylesheet and a script, concatenating it in C++ costs RAM you do not have and makes every edit a recompile. Put the file on flash and serve it from there — that is what Filesystem on flash is for, and it is the starting point for every page after this one.

On your S3
ChipXtensa LX7 · 2 × 240 MHz
Board settingESP32S3 Dev Module
Default I2CSDA 8 · SCL 9
Watch out forThe port vanishes after upload

The code

One route and a fallback. `server.on` puts a path in the table; `handleClient()` is the line that actually runs what is in it.

hello_server.ino
#include <WiFi.h>
#include <WebServer.h>

WebServer server(80);              // 80 is what a browser assumes

void setup() {
  Serial.begin(115200);
  WiFi.begin("your-network", "your-password");
  while (WiFi.status() != WL_CONNECTED) delay(250);
  Serial.println(WiFi.localIP());  // type this into a browser

  server.on("/", HTTP_GET, [] {
    server.send(200, "text/html", "<h1>Lonely Binary</h1>");
  });

  server.onNotFound([] {
    server.send(404, "text/plain", "no such page");
  });

  server.begin();
}

void loop() {
  server.handleClient();           // delete this line and nothing is ever answered
}

This server answers one request at a time, from loop(). Every millisecond loop() spends elsewhere is a millisecond the browser spends waiting.

When it does not work

The browser spins forever and never shows anything

The board is reachable and nothing is answering. Either handleClient() is missing from loop(), or something in loop() is blocking - a delay, a sensor that waits, a while loop of your own. The connection is accepted by the TCP stack either way, which is why it hangs instead of failing.

The serial monitor prints 0.0.0.0 as the address

Wi-Fi has not finished joining. Wait on WiFi.status() until it returns WL_CONNECTED before reading localIP, rather than printing it once at the top of setup.

One path works and everything else is blank rather than a 404

No onNotFound handler is registered, so unmatched paths get an empty response and the browser shows a white page. Register one, even if it only says the path is wrong - a blank page tells you nothing about which end is broken.

It loads on the laptop and not on the phone

They are on different networks, or the router has client isolation on for the guest band. The board only has a private address, so both devices have to be on the same subnet for it to be reachable at all.

Where this goes next

A fixed string proves the plumbing. The next page puts a number on it that changes.

A reading on the page

Edit this page — content/esp32/web-server-hello-world.mdx

Discuss this article

Ask about this page. The answer stays here, on the page it belongs to, for whoever hits the same wall next.

Browse ESP32 on the forum