ESP32 HTTP Client the Easy Way & Low Memory Footprint
A high-performance, fluent, and object-oriented HTTP client for ESP32 with direct JSON binding capabilities.
Official Documentation Get started quickly with the Quick Start Guide or explore the API Reference for detailed usage.
- Why this library?
- Performance & Comparison
- Key Features
- Quick Start
- Initialization
- Usage
- Examples
- API Reference
Writing HTTP requests on embedded systems shouldn't feel like a chore. The standard approach forces you to manage connection states, handle string buffers manually, and allocate large chunks of RAM just to parse a simple JSON response.
ESP32-HTTP-Client acts as a bridge between your variables and your API. You don't "parse" JSON, you tell the client where to put the data.
A typical request with the Arduino SDK looks like this:
- Initialize
HTTPClient. - Make the request.
- Check error codes.
- Call
http.getString(), allocating a largeStringon the heap. - Create a
DynamicJsonDocument, allocating even more RAM. - Call
deserializeJson(). - Extract values manually.
- Hope you didn't run out of heap.
// One line. Zero intermediate strings. Direct memory binding.
client.get("/sensor").getBody("temperature", &myFloatVariable);The following data is the result of a benchmark running 100 consecutive HTTP GET requests with JSON payloads on an ESP32 using the public JSONPlaceholder /users endpoint as the test source.
JSONPlaceholder /users endpoint
| Metric / Feature | Standard (HTTPClient + ArduinoJson) | ESP32-HTTP-Client | Comparison |
|---|---|---|---|
| Memory Usage (Heap per req) | ~58.2 KB | ~0.0 KB (15 bytes) | ⬇ ~99.9% less RAM per request |
| Avg. RAM Footprint (Estimate) | 34.2% | 24.3% | ⬇ ~29% less overall RAM used |
| Absolute Min. Free Heap | 114.3 KB | 128.6 KB | ⬆ Safer for large applications |
| Execution Time (Average) | ~750 ms | ~59 ms | 🚀 ~12x faster (Native Keep-Alive) |
| Code Verbosity | High (~15 lines of boilerplate) | Low (1 fluent chain) | ⬇ Clean & maintainable code |
| JSON Parsing | Requires deserializeJson() |
Automatic, direct binding | ⬆ No JSON document allocation |
Note
Execution Time & Keep-Alive: Because ESP32-HTTP-Client safely reuses the underlying TLS connection and parses the response directly from the network stream (with native Transfer-Encoding: chunked decoding), it avoids the massive penalty of repeatedly establishing TLS handshakes. This makes it over 10x faster than the traditional approach while keeping the memory footprint exceptionally low.
- Fluent chaining — build requests naturally:
.get().query().getBody(). - Direct injection — JSON values are written straight into standard C types (
int,float,bool,char*). - Zero buffering — the response stream is parsed in place; the full payload is never stored.
- Full REST support —
GET,POST,PUT,PATCH, andDELETEare all first-class citizens. - IoT ready — designed for connecting ESP32 devices to cloud backends, Firebase, AWS API Gateway, or custom servers.
#include <WiFi.h>
#include "ESP32HTTPClient.h"
ESP32HTTPClient client("https://jsonplaceholder.typicode.com");
void setup() {
Serial.begin(115200);
WiFi.begin("SSID", "PASS");
while (WiFi.status() != WL_CONNECTED) delay(100);
int userId = 0;
// API returns: { "userId": 1, "id": 1, "title": "..." }
client.get("/todos/1").getBody("userId", &userId);
Serial.printf("User ID fetched from API: %d\n", userId);
}
void loop() {}ESP32HTTPClient client("https://api.example.com");Specify the port as the second argument if your API runs on a non-standard port.
ESP32HTTPClient client("http://my-local-server.local", 8080);float temperature;
bool active;
// Produces: GET /climate?room=living_room&sensor=dht22
client.get("/climate")
.query("room", "living_room")
.query("sensor", "dht22")
.getBody("temp", &temperature) // binds "temp": 24.5
.getBody("active", &active); // binds "active": trueint newId;
// Body: { "name": "Pedro", "role": "admin", "age": 21 }
client.post("/users")
.body("name", "Pedro")
.body("role", "admin")
.body("age", 21)
.getBody("id", &newId);Use dot notation to navigate nested objects.
char val[32];
// Response: { "level0": { "level1": "val2" } }
client.get("/nested")
.getBody("level0.level1", val, sizeof(val));Use a numeric index as a path segment to address array elements.
char city[32];
// Response: [ { "address": { "city": "Gwenborough" } }, { "address": { "city": "Wisokyburgh" } } ]
client.get("/users")
.getBody("1.address.city", city, sizeof(city)); // resolves the second elementBind to an Arduino String to capture an entire object or sub-array for manual processing.
String entireArray;
String specificUser;
client.get("/users")
.getBody("", &entireArray) // captures the root-level array
.getBody("1", &specificUser); // captures the second user objectWarning
Pulling complete objects or arrays into an Arduino String causes dynamic memory reallocation as the raw JSON is copied character by character. Avoid this pattern with large payloads, as it can fragment or exhaust the device heap.
Note
If a key is missing, misspelled, or the path does not exist in the response, the target variable is left unchanged. The library will not crash.
By default, the client automatically maintains a persistent TCP/TLS connection across requests (HTTP Keep-Alive). This drastically improves performance for subsequent requests to the same server, but it holds onto the connection memory buffers (e.g., ~45KB for an active TLS tunnel).
To manually close the connection and free this memory when you are done making requests, call end():
client.end(); // Closes the connection and frees TLS RAM// Update a resource
client.update("/lights/1").body("state", "OFF");
// Delete a resource
client.del("/logs/system_error.log");Runnable sketches are available in the examples/ directory:
| Sketch | Description |
|---|---|
| SimpleGET | Basic data fetching with GET. |
| PostRequest | Sending a JSON payload with POST. |
| PutRequest | Updating a remote resource with PUT. |
| DeleteRequest | Deleting a remote resource. |
| PortSelection | Connecting to a server on a custom port. |
| NestedJSON | Extracting fields from deeply nested objects. |
| ArrayJSON | Addressing array elements by index. |
| RawArrayJSON | Capturing raw arrays or objects into Arduino Strings. |
| UnixTimestamp | Fetching the current Unix Timestamp from an API. |
The main entry point. Instantiate once with your base URL and reuse across requests.
| Signature | Description | Example |
|---|---|---|
ESP32HTTPClient(baseUrl) |
Creates a client. Port defaults to 80 (HTTP) or 443 (HTTPS). | ESP32HTTPClient client("https://api.example.com"); |
ESP32HTTPClient(baseUrl, port) |
Creates a client targeting a custom port. | ESP32HTTPClient client("http://192.168.1.100", 8080); |
Each method returns a RestRequest that can be chained with .query(), .body(), and .getBody().
| Method | Description | Example |
|---|---|---|
get(path) |
Sends a GET request to baseUrl + path. |
client.get("/todos/1") |
post(path) |
Sends a POST request to baseUrl + path. |
client.post("/users") |
put(path) |
Sends a PUT request to baseUrl + path. |
client.put("/users/1") |
update(path) |
Alias for put(). |
client.update("/lights/1") |
patch(path) |
Sends a PATCH request to baseUrl + path. |
client.patch("/config/wifi") |
del(path) |
Sends a DELETE request to baseUrl + path. |
client.del("/logs/old.log") |
| Method | Description | Example |
|---|---|---|
setContentType(contentType) |
Overrides the Content-Type header used for request bodies. Defaults to application/json. |
client.setContentType("application/x-www-form-urlencoded"); |
setHeader(name, value) |
Registers a custom HTTP header that is sent with every subsequent request. | client.setHeader("Authorization", "Bearer mytoken123"); |
getStatusCode() |
Returns the HTTP status code of the last completed request. | int code = client.getStatusCode(); |
end() |
Closes the persistent TCP/TLS connection and frees its memory buffers. Useful after a burst of requests. | client.end(); |
Returned by every HTTP method on ESP32HTTPClient. All builder methods return RestRequest&, enabling fluent chaining. The underlying HTTP request is dispatched on the first call to .getBody(), or automatically when the object goes out of scope.
| Method | Description | Example |
|---|---|---|
query(key, value) |
Appends a URL query parameter. Supports int, float, double, bool, long, and const char*. Chainable. |
client.get("/search").query("q", "esp32").query("limit", 10) |
body(key, value) |
Adds a field to the JSON request body. Supports the same types as query(). Chainable. |
client.post("/users").body("name", "Pedro").body("age", 21) |
getBody() is overloaded for each supported C type. It registers a binding between a JSON key path and a target variable. Use dot notation for nested fields and numeric segments for array indices.
| Method | Description | Example |
|---|---|---|
getBody(key, int* target) |
Binds a JSON integer to *target. |
client.get("/data").getBody("count", &myInt) |
getBody(key, float* target) |
Binds a JSON number to a float. |
client.get("/sensor").getBody("temp", &myFloat) |
getBody(key, double* target) |
Binds a JSON number to a double. |
client.get("/sensor").getBody("voltage", &myDouble) |
getBody(key, bool* target) |
Binds a JSON boolean to *target. |
client.get("/status").getBody("active", &myBool) |
getBody(key, long* target) |
Binds a JSON integer to a long. |
client.get("/stats").getBody("timestamp", &myLong) |
getBody(key, char* target, size_t maxLen) |
Copies a JSON string into a char buffer, up to maxLen bytes. |
client.get("/user").getBody("name", myChar, sizeof(myChar)) |
getBody(key, String* target) |
Copies a raw JSON object or array into an Arduino String. Pass "" to capture the entire response. |
client.get("/users").getBody("", &entireJson) |
Note
If a key is missing or the path does not exist, the target variable is left unchanged. No exception is thrown and no crash occurs.
int userId;
float temperature;
char city[32];
client.post("/report")
.body("device", "esp32-cam")
.body("floor", 3)
.getBody("userId", &userId) // int — root field
.getBody("sensor.temp", &temperature) // float — nested object
.getBody("0.address.city", city, sizeof(city)); // char* — array index + nestedIf this library saved you time, consider leaving a star ⭐ on the repository.