Learning Goals 5 min
The board can now connect to the network. Today it fetches a real web page like a browser would — your first HTTP GET. By the end of this lesson you will be able to:
- Use the
HTTPClientlibrary to GET a URL and read the response body as a string. - Check the HTTP status code (200 OK, 404 Not Found, etc.) and handle errors instead of assuming success.
- Fetch a JSON API — for example a public "current time" or "weather" endpoint — and pull a single value out of the response.
Warm-Up 10 min
Plug in the ESP, open yesterday's wifi-diag.ino, confirm it still connects to your network. We're going to add HTTP on top of the connection you already have.
Recall: what's an HTTP GET
When you type a URL into a browser, behind the scenes the browser:
- Looks up the IP of the server (DNS).
- Opens a TCP connection to that IP on port 80 (or 443 for HTTPS).
- Sends a text request like:
GET /api/time HTTP/1.1 Host: example.com
- The server responds with:
HTTP/1.1 200 OK
Content-Type: application/json
Content-Length: 42
{"datetime": "2026-05-26T12:30:00Z"}
That's the whole web. Today your Arduino does the same thing.
New Concept · HTTPClient on ESP 25 min
The HTTPClient library
Both ESP8266 and ESP32 ship with an HTTPClient class that handles the protocol. The API:
#include <ESP8266HTTPClient.h> // ESP8266
// or:
// #include <HTTPClient.h> // ESP32
WiFiClient client;
HTTPClient http;
http.begin(client, "http://example.com/api/time");
int status = http.GET();
if (status == 200) {
String body = http.getString();
Serial.println(body);
}
http.end();Five steps: begin(URL), GET(), check status, get body, end(). The WiFiClient is the underlying socket; it's passed to begin() so the library knows where to send packets.
HTTP status codes
| Code | Class | Meaning |
|---|---|---|
| 200 | 2xx Success | OK — body contains the response. |
| 301 / 302 | 3xx Redirect | The URL moved. Need to follow Location: header. |
| 400 | 4xx Client error | Bad request — your URL or body was malformed. |
| 401 / 403 | 4xx | Auth required / forbidden. |
| 404 | 4xx | Not found — wrong path. |
| 500 | 5xx Server error | Server crashed; try again later. |
| −1 to −11 (HTTPClient) | local error | Returned by the library for "couldn't even open the socket" — DNS failed, network down, etc. |
Always check the status code. Don't assume 200.
HTTP vs HTTPS
HTTP (port 80) is unencrypted plain text. Easy. HTTPS (port 443) is encrypted with TLS. Slow and memory-hungry on ESP8266 (works but uses ~30 KB of RAM during the handshake — close to the 80 KB limit). ESP32 handles HTTPS much more comfortably. For school projects, find an HTTP-allowing API if you can; many APIs are HTTPS-only these days.
Good HTTP-friendly APIs:
http://worldtimeapi.org/api/timezone/Etc/UTC— current time, plain HTTP supported.http://api.open-notify.org/iss-now.json— current position of the ISS.- Your own laptop's Python
http.serveron the same LAN — perfect for testing without internet.
The full GET sketch
#if defined(ESP8266)
#include <ESP8266WiFi.h>
#include <ESP8266HTTPClient.h>
#elif defined(ESP32)
#include <WiFi.h>
#include <HTTPClient.h>
#endif
const char* SSID = "YourNetwork";
const char* PASSWORD = "YourPassword";
void setup() {
Serial.begin(115200);
delay(500);
WiFi.mode(WIFI_STA);
WiFi.begin(SSID, PASSWORD);
while (WiFi.status() != WL_CONNECTED) { delay(500); Serial.print("."); }
Serial.print("\nIP = "); Serial.println(WiFi.localIP());
}
void loop() {
if (WiFi.status() != WL_CONNECTED) {
delay(1000);
return;
}
WiFiClient client;
HTTPClient http;
http.begin(client, "http://worldtimeapi.org/api/timezone/Etc/UTC");
int status = http.GET();
Serial.print("# HTTP "); Serial.println(status);
if (status == 200) {
String body = http.getString();
Serial.println(body);
} else if (status > 0) {
Serial.println("# Unexpected status (server reachable)");
} else {
Serial.print("# Local error: ");
Serial.println(http.errorToString(status));
}
http.end();
delay(10000);
}Parsing JSON without a library
For simple values, you can pull one field with string search. For real parsing, install ArduinoJson. Quick-and-dirty:
String body = http.getString();
int idx = body.indexOf("\"datetime\":");
if (idx >= 0) {
int start = body.indexOf('"', idx + 11) + 1;
int end = body.indexOf('"', start);
String dt = body.substring(start, end);
Serial.print("Time: "); Serial.println(dt);
}Hacky but works for one field. For multi-field parsing, prefer ArduinoJson — clean, robust, just one extra library.
ArduinoJson — the proper way
#include <ArduinoJson.h>
String body = http.getString();
StaticJsonDocument<512> doc;
DeserializationError err = deserializeJson(doc, body);
if (err) {
Serial.print("JSON err: "); Serial.println(err.c_str());
return;
}
const char* dt = doc["datetime"];
Serial.print("Time: "); Serial.println(dt);Pick a JsonDocument size larger than the longest response you expect. 512 bytes is enough for a small API; for bigger responses bump it to 2048 or use a dynamic DynamicJsonDocument on the heap.
Worked Example · Fetch the current ISS position 25 min
Open Notify (open-notify.org) runs a public free API that returns the latitude/longitude of the International Space Station. Plain HTTP supported, no auth, no rate limits.
Step 1 — install ArduinoJson
Library Manager → search "ArduinoJson" (by Benoit Blanchon) → Install.
Step 2 — the sketch
// L03-31 · Fetch current ISS position over HTTP
#if defined(ESP8266)
#include <ESP8266WiFi.h>
#include <ESP8266HTTPClient.h>
#elif defined(ESP32)
#include <WiFi.h>
#include <HTTPClient.h>
#endif
#include <ArduinoJson.h>
const char* SSID = "YourNetwork";
const char* PASSWORD = "YourPassword";
const char* URL = "http://api.open-notify.org/iss-now.json";
void setup() {
Serial.begin(115200);
delay(500);
WiFi.mode(WIFI_STA);
WiFi.begin(SSID, PASSWORD);
while (WiFi.status() != WL_CONNECTED) { delay(500); Serial.print("."); }
Serial.println();
Serial.print("# Connected. IP = "); Serial.println(WiFi.localIP());
}
void loop() {
if (WiFi.status() != WL_CONNECTED) { delay(1000); return; }
WiFiClient client;
HTTPClient http;
http.begin(client, URL);
int status = http.GET();
if (status == 200) {
String body = http.getString();
StaticJsonDocument<384> doc;
DeserializationError err = deserializeJson(doc, body);
if (!err) {
const char* lat = doc["iss_position"]["latitude"];
const char* lon = doc["iss_position"]["longitude"];
Serial.print("ISS at lat "); Serial.print(lat);
Serial.print(", lon "); Serial.println(lon);
} else {
Serial.print("# JSON err: "); Serial.println(err.c_str());
}
} else {
Serial.print("# HTTP "); Serial.println(status);
}
http.end();
delay(15000); // every 15 s
}Step 3 — upload + watch
Open Serial Monitor at 115200. You should see:
# Connected. IP = 172.20.10.3 ISS at lat 21.3461, lon -157.0431 ISS at lat 21.4502, lon -156.8210 ISS at lat 21.5538, lon -156.5979
The ISS moves ~7.7 km/s, so the latitude/longitude changes noticeably between requests. Plug the numbers into Google Maps to see where it is right now.
Step 4 — handle a failure
Disconnect your phone's hotspot mid-run. The next request returns a negative status (network error). Reconnect; requests resume. Test that the sketch survives.
Step 5 — light an LED when the ISS is over your hemisphere
pinMode(LED_BUILTIN, OUTPUT);
// ... after extracting lat:
float latF = atof(lat);
digitalWrite(LED_BUILTIN, (latF > 0) ? LOW : HIGH); // active-low LED on NodeMCUNow the on-board LED is lit while the ISS is in the northern hemisphere — your first IoT "ambient" device. A real product (Ambient Devices' original orb) did exactly this.
Step 6 — fetch from a private endpoint
If you have a laptop on the same LAN, start a Python HTTP server: python -m http.server 8000. Find your laptop's IP. Point the ESP at http://<laptop-IP>:8000/. The ESP fetches the directory listing. You now have round-trip code from the ESP to your laptop — useful for mocking servers during development.
Try It Yourself 15 min
Goal: Print the response size in bytes (http.getSize()) along with the status.
Hint
Serial.print("# HTTP "); Serial.print(status);
Serial.print(" ("); Serial.print(http.getSize()); Serial.println(" bytes)");Goal: Fetch from http://worldtimeapi.org/api/timezone/Asia/Kuala_Lumpur (or your local time zone). Extract the datetime field and print it. Useful as a free real-time clock — no RTC chip needed.
Hint
Same shape as the ISS sketch — change URL and field name. We'll use this in L03-35 for NTP-style time sync.
Goal: Build a tiny weather widget. Use Open-Meteo's free HTTP API: http://api.open-meteo.com/v1/forecast?latitude=3.14&longitude=101.69¤t_weather=true. Extract the current temperature; print it. Update every 5 minutes (respect rate limits).
Hint
The response has current_weather.temperature. Bump StaticJsonDocument to 1024 bytes for safety. Use a non-blocking timer (millis()) for the 5-minute interval so the sketch can do other things in between.
Mini-Challenge · Wrap into a helper 10 min
Every WiFi sketch you write from here on will GET a URL. Extract the boilerplate into a function:
bool getJSON(const char* url, JsonDocument& dest) {
if (WiFi.status() != WL_CONNECTED) return false;
WiFiClient client;
HTTPClient http;
http.begin(client, url);
int status = http.GET();
if (status != 200) {
Serial.print("# GET "); Serial.print(url);
Serial.print(" -> "); Serial.println(status);
http.end();
return false;
}
String body = http.getString();
http.end();
DeserializationError err = deserializeJson(dest, body);
if (err) { Serial.print("# JSON err: "); Serial.println(err.c_str()); return false; }
return true;
}Usage in loop:
StaticJsonDocument<512> doc;
if (getJSON("http://api.open-notify.org/iss-now.json", doc)) {
const char* lat = doc["iss_position"]["latitude"];
Serial.println(lat);
}Two-line call site, all error handling tucked away. Save as http-helpers.h for re-use in L03-32+.
Recap 5 min
HTTPClient + WiFiClient = your browser, in 5 lines. begin(url) → GET() → check status → getString() → end(). Status 200 = success; everything else needs handling. JSON responses parse cleanly with ArduinoJson. HTTPS is heavier — ESP8266 works but eats RAM; ESP32 handles it comfortably. Always check rate limits; always handle network errors. Tomorrow we flip the direction: the ESP becomes a tiny web server, and a browser on your network fetches data from it.
- HTTP (HyperText Transfer Protocol)
- The protocol of the web. Plain-text requests and responses over TCP. Default port 80.
- HTTPS
- HTTP over TLS encryption. Port 443. Required by most modern public APIs.
- HTTPClient
- The library that handles HTTP requests on ESP boards.
ESP8266HTTPClient.hon ESP8266;HTTPClient.hon ESP32. - WiFiClient
- A TCP socket abstraction. Passed to HTTPClient's
begin()so it knows where to send packets. - GET
- The HTTP verb for "fetch a resource". No body in the request. Most APIs use it for read operations.
- POST
- The HTTP verb for "create / submit". Has a body. Used for sending data to a server. We'll meet it in L03-36 (Webhooks).
- Status code
- A 3-digit number the server returns. 2xx = success, 3xx = redirect, 4xx = client error, 5xx = server error. Negative codes from HTTPClient = local error.
- JSON
- JavaScript Object Notation — text format for structured data. The most common API response format.
{"key": "value"}. - ArduinoJson
- Industry-standard library by Benoit Blanchon for parsing/generating JSON on Arduino. Install via Library Manager.
- Rate limit
- The maximum requests per unit time an API allows. Exceeding = blocked or 429 status. Always respect.
Homework 5 min
- Save the ISS sketch as
http-iss.ino. - Pick one free HTTP API and fetch one value from it. Write down: URL, status code, the value you extracted, the latency (use
millis()around the GET). - Read ahead to ARD-L03-32 (A Tiny Web Server). Tomorrow your ESP serves a webpage instead of fetching one.
Bring back next class:
- Saved HTTP sketch.
- Your API + value + latency note.