Servidor API REST de ESP32 con JSON - Tutorial Completo

Ejemplo de WebServerJson - Servidor API REST

Visión general

Este ejemplo demuestra cómo crear un servidor de API REST en ESP32 que maneja solicitudes y respuestas JSON, perfecto para aplicaciones web modernas y backends de aplicaciones móviles.

Características

  • Puntos finales de la API REST con manejo de solicitudes y respuestas JSON
  • Procesamiento de solicitudes POST con análisis de datos JSON
  • Puntos finales GET para la obtención de datos
  • Respuestas JSON profesionales con códigos de estado HTTP adecuados
  • Manejo de errores con mensajes de error JSON apropiados
  • Soporte CORS para solicitudes entre orígenes

Hardware Requerido

1×Módulo de Desarrollo ESP32 ESP-WROOM-32
1×Cable USB Tipo-A a Tipo-C (para PC USB-A)
1×Cable USB Tipo-C a Tipo-C (para PC USB-C)
1×(Recomendado) Placa de Expansión de Terminales de Tornillo para ESP32
1×(Recomendado) Breakout Expansion Board for ESP32
1×(Recomendado) Divisor de Alimentación para ESP32

Or you can buy the following kits:

1×DIYables ESP32 Starter Kit (ESP32 included)
1×DIYables Sensor Kit (30 sensors/displays)
1×DIYables Sensor Kit (18 sensors/displays)
Divulgación: Algunos de los enlaces proporcionados en esta sección son enlaces de afiliado de Amazon. Podemos recibir una comisión por las compras realizadas a través de estos enlaces sin costo adicional para usted. Apreciamos su apoyo.

Instalación de la biblioteca

Sigue estas instrucciones paso a paso:

  • Si es la primera vez que usas el ESP32, consulta el tutorial sobre configurar el entorno para ESP32 en el IDE de Arduino.
  • Conecta la placa ESP32 a tu computadora usando un cable USB.
  • Abre el IDE de Arduino en tu computadora.
  • Selecciona la placa ESP32 adecuada (p. ej., ESP32) y el puerto COM.
  • Abre el Administrador de Bibliotecas haciendo clic en el icono Administrador de Bibliotecas en el lado izquierdo del IDE de Arduino.
  • Busca Web Server for ESP32 y localiza la biblioteca mWebSockets de DIYables.
  • Haz clic en el botón Instalar para añadir la biblioteca mWebSockets.
librería del servidor web ESP32

Ejemplo de JSON de servidor web

  • En el IDE de Arduino, vaya a Archivo Ejemplos Servidor Web para ESP32 WebServerJson para abrir el código de ejemplo

Puntos finales de la API

OBTENER /api/data

Devuelve un mensaje de éxito con una marca de tiempo en formato JSON.

Respuesta:

{ "status": "success", "message": "GET request received", "timestamp": 12345 }

El valor de la marca de tiempo representa los milisegundos transcurridos desde que se inició el ESP32 (a partir de la función millis()).

POST /api/data

Acepta datos JSON y devuelve el valor de la clave recibida.

Solicitud:

{ "key": "your_value" }

Respuesta:

{ "status": "success", "message": "Data received", "received_key": "your_value" }

Instrucciones de configuración

1. Configuración de red

Edita las credenciales de WiFi en el archivo WebServerJson.ino:

const char WIFI_SSID[] = "YOUR_WIFI_SSID"; const char WIFI_PASSWORD[] = "YOUR_WIFI_PASSWORD";

2. Subir código y monitorear la salida

  1. Conecta tu ESP32 a tu computadora
  2. Selecciona la placa y el puerto correctos en Arduino IDE
  3. Sube el sketch WebServerJson.ino
  4. Abre el Monitor Serial (9600 baudios)
  5. Espera la conexión WiFi
  6. Toma nota de la dirección IP mostrada
  7. Si no ves la dirección IP en el Monitor Serial, pulsa el botón de reinicio en la placa ESP32

Uso

Pruebas con cURL

Reemplaza your-esp32-ip con la dirección IP real que se muestra en tu Monitor Serial.

Prueba de la solicitud GET
curl -X GET http://your-esp32-ip/api/data

Please provide the English text you want translated to Spanish.

Command Prompt
C:\Users\youruser>curl -X GET http://your-esp32-ip/api/data { "status": "success", "message": "GET request received", "timestamp": 12345 }

La marca de tiempo muestra los milisegundos desde el inicio del ESP32.

Prueba de una solicitud POST con datos JSON
curl -X POST http://your-esp32-ip/api/data -H "Content-Type: application/json" -d "{\"key\": \"test_value\"}"

Salida esperada:

Command Prompt
C:\Users\youruser>curl -X POST http://your-esp32-ip/api/data -H "Content-Type: application/json" -d "{\"key\": \"test_value\"}" { "status": "success", "message": "Data received", "received_key": "test_value" }
Prueba de solicitud POST con datos diferentes
curl -X POST http://your-esp32-ip/api/data -H "Content-Type: application/json" -d "{\"key\": \"hello_world\"}"

Salida esperada:

Command Prompt
C:\Users\youruser>curl -X POST http://your-esp32-ip/api/data -H "Content-Type: application/json" -d "{\"key\": \"hello_world\"}" { "status": "success", "message": "Data received", "received_key": "hello_world" }
Prueba de JSON inválido (Respuesta de error)
curl -X POST http://your-esp32-ip/api/data -H "Content-Type: application/json" -d "{invalid json}"

Salida esperada:

Command Prompt
C:\Users\youruser>curl -X POST http://your-esp32-ip/api/data -H "Content-Type: application/json" -d "{invalid json}" { "status": "error", "message": "Invalid JSON" }
Prueba de cuerpo JSON ausente (respuesta de error)
curl -X POST http://your-esp32-ip/api/data

Salida esperada:

Command Prompt
C:\Users\youruser>curl -X POST http://your-esp32-ip/api/data { "status": "error", "message": "No JSON data received" }
Prueba de método no soportado (Respuesta de error)
curl -X PUT http://your-esp32-ip/api/data -H "Content-Type: application/json" -d "{\"key\": \"test\"}"

Salida esperada:

Command Prompt
C:\Users\youruser>curl -X PUT http://your-esp32-ip/api/data -H "Content-Type: application/json" -d "{\"key\": \"test\"}" { "status": "error", "message": "Method not allowed" }
Prueba de endpoint inexistente (error 404)
curl -X GET http://your-esp32-ip/api/nonexistent

Salida esperada:

Command Prompt
C:\Users\youruser>curl -X GET http://your-esp32-ip/api/nonexistent <!DOCTYPE html> <html> <head> <meta charset="UTF-8"> <title>404 Not Found</title> </head> <body> <h1>404 - Page Not Found</h1> <p>Method: GET</p> <p>Sorry, we couldn't find that page!</p> <a href="/">Return to Home</a> </body> </html>

Pruebas con Postman

Prueba de solicitud GET
  1. Crear una nueva solicitud GET
  2. Establece la URL en http://your-esp32-ip/api/data
  3. Envía la solicitud
  4. Verifica que la respuesta contenga el estado, el mensaje y la marca de tiempo
Solicitud POST de prueba
  1. Crear una nueva solicitud POST
  2. Establece la URL a http://your-esp32-ip/api/data
  3. Añade la cabecera: Content-Type: application/json
  4. Añade el cuerpo JSON: {"key": "test_value"}
  5. Envía la solicitud
  6. Verifica que la respuesta muestre el valor de la clave recibido

Pruebas de respuestas de error

Prueba el manejo de errores enviando solicitudes inválidas como se muestra en el código de ejemplo:

Datos JSON faltantes
curl -X POST http://your-esp32-ip/api/data

{"status": "error","message": "No se recibieron datos JSON"}

Formato JSON inválido
curl -X POST http://your-esp32-ip/api/data -H "Content-Type: application/json" -d "{invalid json}"

{"status": "error","message": "JSON inválido"}

Campo clave faltante
curl -X POST http://your-esp32-ip/api/data -H "Content-Type: application/json" -d "{\"other_field\": \"value\"}"

Esperado: La clave por defecto será "none" según el código de ejemplo: doc["key"] | "none"

Método HTTP no soportado
curl -X DELETE http://your-esp32-ip/api/data

{"status": "error","message": "Método no permitido"}

Explicación del código

Configuración de rutas

// Configure API routes server.addRoute("/api/data", handleApiData);

Firma de la función manejadora

Todas las funciones de manejador deben seguir esta firma:

void handleApiData(WiFiClient& client, const String& method, const String& request, const QueryParams& params, const String& jsonData) { // Handler implementation }

Detección de métodos y procesamiento de JSON

void handleApiData(WiFiClient& client, const String& method, const String& request, const QueryParams& params, const String& jsonData) { Serial.print("[API] "); Serial.print(method); Serial.print(" request received"); if (method == "POST") { if (jsonData.length() == 0) { Serial.println("Error: No JSON data received"); client.println("HTTP/1.1 400 Bad Request"); client.println("Content-Type: application/json"); client.println("Connection: close"); client.println(); client.print("{\"status\": \"error\",\"message\": \"No JSON data received\"}"); return; } StaticJsonDocument<200> doc; DeserializationError error = deserializeJson(doc, jsonData); if (!error) { const char* key = doc["key"] | "none"; String response = JSON_RESPONSE; response.replace("%KEY%", key); server.sendResponse(client, response.c_str(), "application/json"); } else { client.println("HTTP/1.1 400 Bad Request"); client.println("Content-Type: application/json"); client.println("Connection: close"); client.println(); client.print("{\"status\": \"error\",\"message\": \"Invalid JSON\"}"); } } else if (method == "GET") { String response = JSON_GET_RESPONSE; response.replace("%TIMESTAMP%", String(millis())); server.sendResponse(client, response.c_str(), "application/json"); } else { client.println("HTTP/1.1 405 Method Not Allowed"); client.println("Content-Type: application/json"); client.println("Connection: close"); client.println(); client.print("{\"status\":\"error\",\"message\":\"Method not allowed\"}"); } }

Ejemplos de integración

Frontend de JavaScript

// Control LED async function controlLED(action) { try { const response = await fetch('http://your-esp32-ip/api/led', { method: 'POST', headers: { 'Content-Type': 'application/json', }, body: JSON.stringify({ action: action }) }); const result = await response.json(); console.log('LED control result:', result); } catch (error) { console.error('Error:', error); } } // Get sensor data async function getSensorData() { try { const response = await fetch('http://your-esp32-ip/api/sensor'); const data = await response.json(); console.log('Sensor data:', data); } catch (error) { console.error('Error:', error); } }

Cliente de Python

import requests import json

Control de LED

def control_led(action): url = "http://your-esp32-ip/api/led" data = {"action": action} response = requests.post(url, json=data) return response.json()

Obtener estado

def get_status(): url = "http://your-esp32-ip/api/status" response = requests.get(url) return response.json()

Uso

result = control_led("on") print(result) status = get_status() print(status)

Manejo de errores

Códigos de estado HTTP

  • 200: Éxito
  • 400: Solicitud inválida (JSON inválido, parámetros ausentes)
  • 404: Endpoint no encontrado
  • 405: Método no permitido
  • 500: Error interno del servidor

Formato de respuesta de error

Basado en el código de ejemplo real, diferentes errores devuelven mensajes específicos:

Error de datos JSON faltantes
{ "status": "error", "message": "No JSON data received" }

Devuelto cuando: se envía una solicitud POST sin cuerpo JSON

Error de formato JSON inválido
{ "status": "error", "message": "Invalid JSON" }

Se devuelve cuando: Los datos JSON no se pueden analizar (errores de sintaxis)

Error de método no permitido
{ "status": "error", "message": "Method not allowed" }

Devuelto cuando: se utilizan métodos HTTP no soportados (PUT, DELETE, PATCH, etc.)

Error 404: No encontrado
<!DOCTYPE html> <html> <head> <meta charset="UTF-8"> <title>404 Not Found</title> </head> <body> <h1>404 - Page Not Found</h1> <p>Method: [HTTP_METHOD]</p> <p>Sorry, we couldn't find that page!</p> <a href="/">Return to Home</a> </body> </html>

Se devuelve cuando: Al acceder a puntos finales inexistentes

Personalización

Añadiendo nuevas funciones de manejador

// Create additional handler for a new endpoint void handleApiStatus(WiFiClient& client, const String& method, const String& request, const QueryParams& params, const String& jsonData) { if (method == "GET") { String json = "{\"status\":\"online\",\"uptime\":" + String(millis() / 1000) + "}"; server.sendResponse(client, json.c_str(), "application/json"); } else { client.println("HTTP/1.1 405 Method Not Allowed"); client.println("Content-Type: application/json"); client.println("Connection: close"); client.println(); client.print("{\"status\":\"error\",\"message\":\"Method not allowed\"}"); } } // Register the new route in setup() server.addRoute("/api/status", handleApiStatus);

Respuestas JSON basadas en plantillas

El ejemplo utiliza plantillas de cadenas para un formato JSON consistente:

const char JSON_RESPONSE[] PROGMEM = R"rawliteral( { "status": "success", "message": "Data received", "received_key": "%KEY%" } )rawliteral"; // Usage in handler String response = JSON_RESPONSE; response.replace("%KEY%", extractedValue); server.sendResponse(client, response.c_str(), "application/json");

Solución de problemas

Problemas comunes

Problemas de conexión

Si no puedes conectarte a los puntos finales de la API:

  1. Verifica que tu ESP32 esté conectado a WiFi (consulta el Monitor Serial)
  2. Asegúrate de que tu cliente y el ESP32 estén en la misma red
  3. Verifica que la dirección IP sea correcta
  4. Verifica que el ESP32 no se haya reiniciado (lo cual cambiaría la IP)

Errores de análisis de JSON

Si recibes respuestas de 'JSON inválido':

  1. Asegúrate de que el encabezado Content-Type esté establecido en application/json
  2. Verifica que la sintaxis JSON sea válida
  3. Verifica que los caracteres especiales estén debidamente escapados
  4. Asegúrate de que la carga útil JSON no sea demasiado grande (límite actual: 200 bytes)

Problemas con la solicitud POST

Si las solicitudes POST devuelven "No se recibieron datos JSON":

  1. Verifica que estés enviando un cuerpo JSON con la solicitud
  2. Comprueba que el encabezado Content-Length esté establecido correctamente
  3. Asegúrate de que el método HTTP sea realmente POST
  4. Prueba con un JSON simple como {"key": "test"}

Problemas de memoria

Si el ESP32 deja de responder:

  1. Monitorear el uso de memoria - StaticJsonDocument utiliza 200 bytes
  2. Reducir el tamaño de la plantilla JSON_RESPONSE si es necesario
  3. Considera usar DynamicJsonDocument para datos de tamaño variable
  4. Verificar fugas de memoria en funciones de manejador personalizadas

Consejos de rendimiento

Optimizar el procesamiento de JSON

// Use appropriate document size for your data StaticJsonDocument<200> doc; // For small JSON objects StaticJsonDocument<1024> doc; // For larger JSON objects // Reuse string objects to reduce memory allocation String response; response.reserve(256); // Pre-allocate space response = JSON_RESPONSE; response.replace("%KEY%", value);

Gestión eficiente de respuestas

// Send responses directly for simple cases client.print(F("{\"status\":\"ok\",\"value\":")); client.print(sensorValue); client.print(F("}")); // Use PROGMEM for large static responses const char LARGE_RESPONSE[] PROGMEM = R"({ "status": "success", "data": { ... } })";

Próximos pasos

  • Explora WebServerQueryStrings.ino para el manejo de parámetros de URL
  • Prueba WebServerWithWebSocket.ino para la comunicación en tiempo real
  • Considera la integración con bases de datos o servicios en la nube

Recursos de aprendizaje

※ NUESTROS MENSAJES

  • No dude en compartir el enlace de este tutorial. Sin embargo, por favor no use nuestro contenido en otros sitios web. Hemos invertido mucho esfuerzo y tiempo en crear el contenido, ¡por favor respete nuestro trabajo!