api-rest-con-esp8266-axios-vuejs-y-vuetify

Cómo hacer un API Rest con ESP32, Axios, VueJs y Vuetify

  • 4 min

VueJs, Vuetify y Axios permiten crear una interfaz web para un ESP32 con API REST y estética Material Design.

El ejemplo está orientado a ESP32. En muchos casos también puede adaptarse a ESP8266 cambiando librerías y algunos detalles de pines.

La arquitectura combina tres piezas: el ESP32 como backend que sirve el API REST, Axios como cliente HTTP en el navegador y VueJs/Vuetify para construir la interfaz.

Nuestra aplicación podría ser un buen ejemplo de arquitectura pero… reconozcamos que era fea como ella sola. ¡Pero esto está a punto de cambiar! Para ello vamos a incorporar a nuestro coctel Vuetify, que ya vimos en esta entrada.

esp8266-vue-vuetify-axios-resultado

El objetivo es conseguir una funcionalidad parecida a la anterior, es decir una demo que muestre cómo realizar las distintas acciones a través del API Rest, pero con esta estética más moderna y adecuada.

El código del backend, es decir, del ESP32, mantiene la misma estructura que en los ejemplos anteriores de API REST. Aquí nos centraremos en la parte del frontend.

Lo que va a cambiar es la página web servida.

El fichero index.html pasa a ser

<!DOCTYPE html>
<html>
  <head>
    <title>ESP32 Vuetify + Axios</title>
    <meta charset="utf-8">
      <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <!-- Desde CDN -->
    <!--<link href='https://fonts.googleapis.com/css?family=Roboto:300,400,500,700|Material+Icons' rel="stylesheet">-->
    <!--<link href="https://cdn.jsdelivr.net/npm/[email protected]/dist/vuetify.min.css" rel="stylesheet">-->

    <link href="./vendor/google-fonts.css" rel="stylesheet">
    <link href="./vendor/vuetify.min.css" rel="stylesheet">
   </head>
<body>
    <div id="app">
      <v-app id="inspire">
        <v-container style="max-width: 600px">
          <v-form>
            <v-container>
              <v-layout row>
                <v-flex xs12 sm6 md3>
                  <v-btn color="info" @click="getAll">Get All</v-btn>
                </v-flex>
              </v-layout>

              <v-layout row>
                <v-flex xs12 sm6 md3>
                  <v-btn color="info" @click="getById">Get</v-btn>
                </v-flex>

                <v-flex xs12 sm6 md3>
                  <v-text-field v-model.number="getId" label="Id" solo type="number"></v-text-field>
                </v-flex>
              </v-layout>

              <v-layout row>
                <v-flex xs12 sm6 md3>
                  <v-btn color="info" @click="updateById">Update</v-btn>
                </v-flex>

                <v-flex xs12 sm6 md3>
                  <v-text-field v-model.number="updateId" label="Id" solo type="number"></v-text-field>
                </v-flex>

                <v-flex xs12 sm6 md3>
                  <v-text-field v-model="updateText" label="Text" solo></v-text-field>
                </v-flex>
              </v-layout>
            </v-container>
          </v-form>

          <v-divider class="mb-3"></v-divider>

          <v-text-field v-model="newItem" label="Add new item" solo @keydown.enter="create">
            <v-fade-transition slot="append">
              <v-icon v-if="newItem" @click="create">add_circle</v-icon>
            </v-fade-transition>
          </v-text-field>

          <v-card>
            <v-toolbar color="cyan" dark>
              <v-toolbar-title>Total: {{ items.length }}</v-toolbar-title>
            </v-toolbar>

            <v-fade-transition class="py-0" group tag="v-list">
              <template v-for="(item, i) in items">
                <v-divider v-if="i !== 0" :key="`${i}-divider`" ></v-divider>

                <v-list-tile :key="`${i}-${item.text}`">
                  <v-list-tile-action>
                    <v-list-tile-title v-text="item.id"></v-list-tile-title>
                  </v-list-tile-action>

                  <v-list-tile-action>
                    <v-list-tile-content>

                      <v-list-tile-title v-text="item.text"></v-list-tile-title>
                    </v-list-tile-content>
                  </v-list-tile-action>

                  <v-spacer></v-spacer>
                  <div>
                    <v-btn color="error" v-model="item" @click="remove(item)">Delete</v-btn>
                  </div>
                </v-list-tile>
              </template>
            </v-fade-transition>

            <v-snackbar v-model="snackbar" :bottom="true" :timeout="timeout">
              {{ text }}
              <v-btn color="pink" flat @click="snackbar = false">
                Close
              </v-btn>
            </v-snackbar>
          </v-card>
        </v-container>
      </v-app>
    </div>

  <!-- Desde CDN -->
  <!--<script src="https://cdn.jsdelivr.net/npm/[email protected]/dist/vue.min.js"></script>-->
  <!--<script src="https://cdn.jsdelivr.net/npm/[email protected]/dist/vuetify.min.js"></script>-->
  <!--<script src="https://cdn.jsdelivr.net/npm/[email protected]/dist/axios.min.js"></script>-->

  <script type="text/javascript" src="./vendor/vue.min.js"></script>
  <script type="text/javascript" src="./vendor/vuetify.min.js"></script>
  <script type="text/javascript" src="./vendor/axios.min.js"></script>
    
  <script type="text/javascript" src="./js/API.js"></script>
  <script type="text/javascript" src="./js/app.js"></script>
   </body>
</html>
Copied!

El fichero JavaScript de la página también debe modificarse para adaptarse al nuevo Framework Vuetify, quedando así.

new Vue({
  el: '#app',
  
  data: () => ({
    items: [
      {
        id: 0,
        done: false,
        text: 'Init 0'
      }
    ],
    
    newItem: null,
    getId: null,
    updateId: null,
    updateText: null,
    
    snackbar: false,
    timeout: 1500,
    text: '',
    
    lastId : 0,
  }),
  
  computed: {
  },
  
  methods: {
    create () {
      lastId = Math.max(...this.items.map(d => d.id));
      this.items.push({
        id: ++this.lastId,
        done: false,
        text: this.newItem
      })
      
      API_post(this.newItem)
      this.newItem = null
    },
    getAll() {
      this.text = "Getting all"
      this.snackbar = true
      
      API_getAll()
      
      lastId = Math.max(...this.items.map(d => d.id));
      this.items.push({id: ++this.lastId, text: 'Loaded all '})
      this.items.push({id: ++this.lastId, text: 'Loaded all '})
    },
    getById() {
      if(this.getId == null) return;
      if(((typeof this.items.find(x => x.id === this.getId) !== 'undefined'))) return;
      
      this.text = "Getting by Id " + this.getId
      this.snackbar = true
      
      API_get(this.getId)
      
      this.items.push({id: this.getId, text: 'Loaded by Id'})
    },
    updateById(id) {
      if(this.updateId == null || this.updateId == null) return;
      
      this.text = "Updating " + this.updateId + " with " + this.updateText
      this.snackbar = true
      
      API_put(this.updateId, this.updateText)
      
      this.items.find(x => x.id === this.updateId).text = this.updateText
    },
    remove(item) {
      this.text = "Deleting " + item.id
      this.snackbar = true
      
      API_delete(item.id)
      
      this.items.splice(this.items.indexOf(item), 1)
    }
  }
})
Copied!

Resultado

Subimos nuestros contenidos al SPIFFS como estamos acostumbrados, y pulsamos los distintos botones para comprobar que todo funciona correctamente en la consola de desarrollo del navegador.

esp8266-vuejs-axios-console

Así como que llegan correctamente al ESP32 como backend, a través del terminal del puerto serie.

esp8266-vuejs-axios-serial

Con esto hemos integrado API REST, JSON, Axios, Vue y Vuetify en una demo funcional y bastante más agradable de usar. La misma estructura se puede adaptar a paneles de control, configuradores o pequeñas interfaces de mantenimiento para proyectos con ESP32.

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