como-servir-y-consumir-un-api-rest-con-esp8266-y-axios

Cómo servir y consumir un Api Rest con ESP8266 o ESP32 y Axios

Continuamos con la sección de entradas dedicadas al ESP8266 y el ESP32. En esta ocasión nos toca ver cómo integrar la popular librería AXIOS para realizar peticiones AJAX desde un cliente web a un API REST servido desde el ESP8266.

Haremos referencia al ESP8266, pero el mismo código es compatible para el ESP32, ajustando el nombre de las librerías. Al final tenéis el código tanto para el ESP8266 como para el ESP32.

Hace varias entradas que vimos cómo servir un API REST desde el ESP8266. Por su parte, en las dos últimas entradas hemos hecho un pequeño “parón” para ver los framework VueJS y Vuetify.

Por supuesto, el objetivo de las próximas entradas es integrar todos estos componentes para servir una aplicación en VueJS/Vuetify que comunique con un API REST servida desde el ESP8266. Así que en esta entrada veremos cómo realizar esta comunicación a través del API REST.

Para ello vamos a ver otro componente, la librería AXIOS, una popular librería en Javascript para realizar peticiones. AXIOS nos va a permitir hacer las peticiones AJAX en el cliente de una forma más “limpia”.

Para ver su uso vamos a basarnos en la entrada en la que serviamos nuestro API REST de ejemplo, correctamente formateado, y vamos a adaptarla para ilustrar la comunicación con el cliente usando AXIOS. Lógicamente, el cambio respecto a esta entrada afecta principalmente al cliente, por lo que toda la parte backend no va a tener cambios.

Es decir, el programa principal del ESP8266 sigue siendo.

#include <ESP8266WiFi.h>
#include <ESPAsyncWebServer.h>
#include <FS.h>
#include <ArduinoJson.h>

#include "config.h"  // Sustituir con datos de vuestra red
#include "API.hpp"
#include "Server.hpp"
#include "ESP8266_Utils.hpp"

void setup(void)
{
  Serial.begin(115200);
  SPIFFS.begin();
  
  ConnectWiFi_STA();
  
  InitServer();
}

void loop(void)
{
}

Así como la definición del servidor en el fichero ‘Server.hpp’.

AsyncWebServer server(80);

void notFound(AsyncWebServerRequest *request) {
  request->send(404, "text/plain", "Not found");
}

void InitServer()
{
  server.serveStatic("/", SPIFFS, "/").setDefaultFile("index.html");
  
  server.on("/item", HTTP_GET, getRequest);
  server.on("/item", HTTP_POST, [](AsyncWebServerRequest * request){}, NULL, postRequest);
  server.on("/item", HTTP_PUT, [](AsyncWebServerRequest * request){}, NULL, putRequest);
  server.on("/item", HTTP_PATCH, [](AsyncWebServerRequest * request){}, NULL, patchRequest);
  server.on("/item", HTTP_DELETE, deleteRequest);
  
  server.onNotFound(notFound);

  server.begin();
    Serial.println("HTTP server started");
}

Y del API REST servido que tenemos definido en el fichero ‘API.hpp’

#include "ESP8266_Utils_APIREST.hpp"

const char* PARAM_FILTER = "filter";

void getAll(AsyncWebServerRequest *request)
{
  String message = "Get All";
  Serial.println(message);
  request->send(200, "text/plain", message);
}

void getFiltered(AsyncWebServerRequest *request)
{
  String message = "Get filtered by " + request->getParam(PARAM_FILTER)->value();
  Serial.println(message);
  request->send(200, "text/plain", message);
}

void getById(AsyncWebServerRequest *request)
{
  int id = GetIdFromURL(request, "/item/");

  String message = String("Get by Id ") + id;
  Serial.println(message);
  request->send(200, "text/plain", message);
}

void getRequest(AsyncWebServerRequest *request) {
  
  if (request->hasParam(PARAM_FILTER)) {
    getFiltered(request);
  }
  else if(request->url().indexOf("/item/") != -1)
  {
    getById(request);
  }
  else {
    getAll(request);
  }
}

void postRequest(AsyncWebServerRequest * request, uint8_t *data, size_t len, size_t index, size_t total)
{ 
  String bodyContent = GetBodyContent(data, len);
  
  StaticJsonDocument<200> doc;
  DeserializationError error = deserializeJson(doc, bodyContent);
  if (error) { request->send(400); return;}

  String string_data = doc["data"];
  String message = "Create " + string_data;
  Serial.println(message);
  request->send(200, "text/plain", message);
}

void patchRequest(AsyncWebServerRequest * request, uint8_t *data, size_t len, size_t index, size_t total)
{
  int id = GetIdFromURL(request, "/item/");
  String bodyContent = GetBodyContent(data, len);
  
  StaticJsonDocument<200> doc;
  DeserializationError error = deserializeJson(doc, bodyContent);
  if (error) { request->send(400); return;}

  String string_data = doc["data"];
  String message = String("Update ") + id + " with " + string_data;
  Serial.println(message);
  request->send(200, "text/plain", message);
}

void putRequest(AsyncWebServerRequest * request, uint8_t *data, size_t len, size_t index, size_t total)
{
  int id = GetIdFromURL(request, "/item/");
  String bodyContent = GetBodyContent(data, len);
   
  StaticJsonDocument<200> doc;
  DeserializationError error = deserializeJson(doc, bodyContent);
  if (error) { request->send(400); return;}

  String string_data = doc["data"];
  String message = String("Replace ") + id + " with " + string_data;
  Serial.println(message);
  request->send(200, "text/plain", message);
}

void deleteRequest(AsyncWebServerRequest *request) {
  int id = GetIdFromURL(request, "/item/");

  String message = String("Delete ") + id;
  Serial.println(message);
  request->send(200, "text/plain", message);
}

Ahora vamos con el cliente, donde sí vamos a tener cambios importantes.

El fichero ‘index.html’ cambia para incluir la librería AXIOS bien desde CDN (comentada) o desde la memoria del ESP8266.

<!DOCTYPE html>
<html>
  <head>
    <title>ESP8266 Axios</title>
    <meta charset="utf-8">
      <meta name="viewport" content="width=device-width, initial-scale=1.0">
    </head>
    <body>
        <div>
            <div>
                <button onclick="getAllRequest()">Get All</button>
                <button onclick="getFilteredRequest()">Get Filtered</button>
                <button onclick="getByIdRequest()">Get by Id</button>
            </div>
            <div>
                <button onclick="postRequest()">Create</button>
            </div>
            <div>
                <button onclick="patchRequest()">Update</button>
            </div>
            <div>
                <button onclick="putRequest()">Replace</button>
            </div>
            <div>
                <button onclick="deleteRequest()">Delete</button>
            </div>
        </div>
        
        <!-- Desde CDN -->
        <!--<script src="https://cdn.jsdelivr.net/npm/axios@0.18.0/dist/axios.min.js"></script>-->
        <script type="text/javascript" src="./vendor/axios.min.js"></script>

        <script src="./js/API.js"></script>
    </body>
</html>

Por otro lado, hemos externalizado todas las funciones de llamada a nuestro API REST en el fichero ‘API.js’, que contiene las funciones de ejemplo para las respectivas llamadas realizadas empleando la librería AXIOS.

function getAllRequest()
{
  axios.get('item')
    .then(function (response) {
    console.log(response);
    })
    .catch(function (error) {
    console.log(error);
    })
    .then(function () {
    });
}

function getFilteredRequest()
{
  axios.get('item', {
    params: {
      filter : 'myFilter'
    }
    })
    .then(function (response) {
    console.log(response);
    })
    .catch(function (error) {
    console.log(error);
    })
    .then(function () {
    });
}

function getByIdRequest()
{
  id = 10;
  axios.get('item/' + id)
    .then(function (response) {
    console.log(response);
    })
    .catch(function (error) {
    console.log(error);
    })
    .then(function () {
    });
}

function postRequest()
{
  axios.post('item', {
    data: 'NewItem'
    })
    .then(function (response) {
    console.log(response);
    })
    .catch(function (error) {
    console.log(error);
    })
    .then(function () {
    });
}

function putRequest()
{
  id = 10;
  axios.put('item/' + id, {
    data: 'NewItem'
    })
    .then(function (response) {
    console.log(response);
    })
    .catch(function (error) {
    console.log(error);
    })
    .then(function () {
    });
}

function patchRequest()
{
  id = 10;
  axios.patch('item/' + id, {
    data: 'NewItem'
    })
    .then(function (response) {
    console.log(response);
    })
    .catch(function (error) {
    console.log(error);
    })
    .then(function () {
    });
}

function deleteRequest()
{
  id = 10;
  axios.delete('item/' + id)
    .then(function (response) {
    console.log(response);
    })
    .catch(function (error) {
    console.log(error);
    })
    .then(function () {
    });
}

Resultado

Si cargamos todo en el ESP8266, y accedemos a la página web servida, veremos nuestra sencilla (y sí, feísima otra vez) interface para probar que conectamos correctamente con el API REST.

esp8266-apirest-client

Si pulsamos en los distintos botones podremos comprobar como, efectivamente, el backend en el ES8266 recibe correctamente las peticiones y muestra los datos oportunos en la consola de comandos.

esp8266-client-api-rest-nodejs

Como vemos, es muy sencillo integrar AXIOS en el ESP8266. Esta pequeña librería nos va a simplificar mucho el trabajo en nuestros proyectos, en especial al trabajar con API REST, haciendo nuestro código más sencillo y mantenible.

Por supuesto, es sólo un ejemplo para ilustrar la comunicación. En un proyecto real habría que definir el API REST, hacer el interface web completo, y hacer las acciones que queramos en el backend. Pero el ejemplo contiene todo el código necesario para hacer las peticiones CRUD (get/post/update/delete) tanto en backend como en frontend.

En las próximas dos entradas continuaremos mejorando este ejemplo, sobre todo la parte del interface web, añadiendo la integración del API REST y AXIOS a nuestras soluciones junto con VueJS y Vuetify ¡Hasta pronto!

Descarga el código

Todo el código de esta entrada está disponible para su descarga en Github.

github-full

Versión para el ESP8266: https://github.com/luisllamasbinaburo/ESP8266-Examples

Versión para el ESP32: https://github.com/luisllamasbinaburo/ESP32-Examples