Language: EN

como-servir-un-api-rest-con-json-desde-esp8266

How to use an ESP8266 or ESP32 as a server for a Rest API with Json

We continue with the series of posts dedicated to the ESP8266 and ESP32 seeing how to serve a correctly structured Rest API from an ESP8266.

We will refer to the ESP8266, but the same code is compatible for the ESP32, adjusting the name of the libraries. At the end you have the code for both the ESP8266 and the ESP32.

In the previous post we saw how to consume a Rest API acting as a client. We remember that the Rest API has become a standard in web communication in which, in a very summarized way, actions are transmitted through HTTP requests and we use the Json format for data exchange.

Just as we learned in the previous post how to connect to an existing Rest API, for example, on another server, to be able to perform actions from the ESP8266, it is interesting that when the ESP8266 acts as a server it provides its own Rest API as a form of communication.

That is precisely what we are going to dedicate this post to, configuring an ESP8266 as a server for a Rest API, interpreting the requests and their data to execute actions, and exchanging information using the Arduino Json library that we saw in this post.

For example, we could return the data from a temperature or humidity sensor. Or perform actions such as moving a robot, or activating a mechanism. Or even, simply communicating and exchanging information with another device, such as a Raspberry PI or even another ESP8266.

Basically, we are going to emulate the Rest API that we use in all the posts, which is available in this link in Node.js version, and that we used as a server in the previous post where the ESP8266.

The code here starts to be somewhat advanced. In reality, we will see that it is not so difficult. But it is interesting that you have read and well read the previous tutorials about the ESP8266.

So let’s go!

Our main program code is really simple, as usual

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

#include "config.h"  // Replace with your network data
#include "API.hpp"
#include "Server.hpp"
#include "ESP8266_Utils.hpp"

void setup() 
{
  Serial.begin(115200);

  ConnectWiFi_STA();

  InitServer();
}

void loop() 
{
}

Most of the ‘magic’ happens in the ‘Server.hpp’ file, where we associate the different verbs and actions available in the example to the corresponding functions of our API.

AsyncWebServer server(80);

void homeRequest(AsyncWebServerRequest *request) {
  request->send(200, "text/plain", "Hello, world");
}

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

void InitServer()
{
  server.on("/", HTTP_GET, homeRequest);
  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");
}

These API functions are defined in the ‘API.hpp’ file (logical, right?).

#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);
  }
}

Finally, following the philosophy of the series to create reusable components to improve the cleanliness of our projects, we have the ‘ESP8266_Utils_APIREST.hpp’ file that we already used in the previous post, with useful functions for our Rest API.

This file contains reusable functions that we can use in our project, and that are used by the ‘API.hpp’ file to do the work of the Rest API.

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);
}

int GetIdFromURL(AsyncWebServerRequest *request, String root)
{
  String string_id = request->url();
  string_id.replace(root, "");
  int id = string_id.toInt();
  return id;
}

String GetBodyContent(uint8_t *data, size_t len)
{
  String content = "";
  for (size_t i = 0; i < len; i++) {
    content .concat((char)data[i]);
  }
  return content;
}

Result

If we load this code into the ESP8266, and make the corresponding requests to our Rest API from Postman, we will see that we receive the responses correctly.

esp8266-servir-api-rest-resultado

We can also verify that everything works correctly from the Arduino serial port console.

esp8266-servir-api-rest-serial-port

There you have it! We have already learned to configure and serve a correctly formatted Rest API from an ESP8266 using Json format for data exchange. You can use the basis of this example in your projects to provide a standardized form of communication.

So, are we done? Not at all! Consuming and, especially, serving Rest APIs is an important milestone on the road, but it is not an end. It is rather a good start. We still have to see how to use it in our projects, for example, to control a robot or make a datalogger.

And for that, in the next posts we will focus on interacting with our brand new Rest APIs from web pages served to the client from the ESP8266. See you soon!

Download the code

All the code from this post is available for download on Github.

github-full

Version for the ESP8266: https://github.com/luisllamasbinaburo/ESP8266-Examples

Version for the ESP32: https://github.com/luisllamasbinaburo/ESP32-Examples