Consumo de una API REST con Vue.js
Hola con tod@s, el día de hoy continuaremos aprendiendo acerca del uso de Vue.js como un framework de javascript para frontend. En este post vamos a crear un cliente para el servicio REST previamente desarrollado con Python y Flask que tenemos disponible en este enlace. ¡Empecemos!
Como paso inicial, vamos a clonar el repositorio de nuestro API REST y seguiremos las instrucciones del mismo para crear el entorno virtual, instalar los requerimientos y ejecutar el servidor para tenerlo activo.
Ahora empezamos a desarrollar nuestros archivos html con el apoyo de las siguientes librerías:
- Vue.js: La librería de Vue mediante su CDN en su documentación oficial.
- Vuex: Para poder desarrollar de mejor manera el consumo de endpoints, así como para mejorar la comunicación entre componentes, usaremos la librería Vuex a traves de su CDN acorde a su documentación oficial.
- Bootstrap: Importaremos la hoja de estilos css boostrap para poder otorgar estilos a nuestros archivos, acorde a su documentación oficial.
- SweetAlert2: Usaremos esta librería para poder simplificar el uso de mensajes de alerta, acorde a su documentación oficial.
De esta manera, vamos a definir nuestro primer archivo denominado index.html de la siguiente manera:
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<meta http-equiv="X-UA-Compatible" content="ie=edge">
<title>Nano-bytes Flask client</title>
<script src="https://cdn.jsdelivr.net/npm/vue/dist/vue.js"></script>
<script src="https://unpkg.com/[email protected]/dist/vuex.js"></script>
<script src="https://cdn.jsdelivr.net/npm/sweetalert2@8"></script>
<link rel="stylesheet" href="https://stackpath.bootstrapcdn.com/bootstrap/4.3.1/css/bootstrap.min.css" integrity="sha384-ggOyR0iXCbMQv3Xipma34MD+dH/1fQ784/j6cY/iJTQUOhcWr7x9JvoRxT2MZw1T" crossorigin="anonymous">
</head>
<body class="bg-dark ">
<img class="rounded mx-auto d-block h-25 p-3" style="width: 120px;" src="https://blog.nano-bytes.com/wp-content/uploads/2019/04/cropped-nano-bytes-logo-transparent.png">
<div id="app" class="container">
<cabecera></cabecera>
<tablatodos></tablatodos>
<editar></editar>
</div>
<script src="config.js"></script>
<script src="main.js"></script>
<script>
function closeForm() {
document.getElementById("editPopUp").style.display = "none";
document.getElementById("list").style.display = "block";
}
</script>
</body>
</html>
Donde podemos definir:
- div id="app" es el elemento que va a ser llamado por la instancia de vue.
- cabecera, tablatodos, editar son componentes de vue que definiremos posteriormente.
- config.js es un archivo de configuración donde podemos configurar: el protocolo de conexión, la ip y el puerto del servidor al cual apuntaremos (si deseamos cambiar los datos del servidor, podemos hacerlo en este archivo).
- main.js donde tendremos todo el desarrollo de vuejs y vuex.
De la misma manera y con las mismas librerías y estructura, crearemos un archivo denominado nuevo.html en donde va a tener el componente cabecera y nuevo que nos servirá para crear un nuevo ToDo:
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<meta http-equiv="X-UA-Compatible" content="ie=edge">
<title>Nano-bytes Flask client</title>
<script src="https://cdn.jsdelivr.net/npm/vue/dist/vue.js"></script>
<script src="https://unpkg.com/[email protected]/dist/vuex.js"></script>
<script src="https://cdn.jsdelivr.net/npm/sweetalert2@8"></script>
<link rel="stylesheet" href="https://stackpath.bootstrapcdn.com/bootstrap/4.3.1/css/bootstrap.min.css" integrity="sha384-ggOyR0iXCbMQv3Xipma34MD+dH/1fQ784/j6cY/iJTQUOhcWr7x9JvoRxT2MZw1T" crossorigin="anonymous">
</head>
<body class="bg-dark ">
<img class="rounded mx-auto d-block h-25 p-3" style="width: 120px;" src="https://blog.nano-bytes.com/wp-content/uploads/2019/04/cropped-nano-bytes-logo-transparent.png">
<div id="app" class="container">
<cabecera></cabecera>
<nuevo></nuevo>
</div>
<script src="config.js"></script>
<script src="main.js"></script>
</body>
</html>
De ahora en adelante nos centraremos únicamente en el archivo main.js que contiene la parte central de nuestro desarrollo.
En esta parte primeramente analizaremos la librería Vuex que nos facilita el manejo de API's, así como la comunicación de componentes Vue entre muchos otros beneficios; sin embargo, nos centraremos en la siguiente gráfica para simplificar la explicación sobre el funcionamiento de Vuex:
Flujo de datos de Vuex (fuente: https://vuex.vuejs.org/)
Lectura recomendada: https://vuex.vuejs.org/
Acorde al flujo de datos anterior, necesitaremos 3 elementos para proceder con el desarrollo:
Instancia de Vuex:
Vuex va a ser el elemento central que consultará, guardará y procesará los datos que serán renderizados por cada componente de vue. Para ello, hace uso de:
- Actions: son los métodos que se comunican con el API.
- Mutations: son los métodos que operan sobre esos datos antes de ser almacenados en el state.
- State: es el storage que almacena y maneja todos los datos.
Una instancia de vuex tiene la siguiente estructura básica:
const store = new Vuex.Store({
state: {
data: ''
},
mutations: {
//Aqui van las mutations como métodos
},
actions: {
//Aquí van los actions como métodos
}
});
Instancia de Vue:
La instancia de Vue básica que ocuparemos se distingue por la llamada al elemento padre (en nuestro caso, nuestro div id="app") y la llamada al store de vuex para que pueda operar sobre los datos:
const app = new Vue({
el: "#app",
store: store
});
Componentes de Vue
Los componentes de Vue contienen los elementos sobre los que interactúa el usuario, así como los elementos de operación y de ciclo de vida de cualquier instancia de vue. Recordemos que al contener los elementos que interactúan con el usuario, un componente necesita de templates html. Una estructura básica de un componente de Vue se ejemplifica de la siguiente manera:
Vue.component('nombreComponente', {
template: //html
`
<div>
<h2>{{ titulo }}</h2>
</div>
`,
computed: {
},
methods: {
}
});
Desarrollo del API client
Una vez que conocemos los elementos necesarios para nuestro desarrollo, vamos a crear la primera parte de nuestro CRUD de ToDo's que corresponde a listar todos los ToDo's que estan almacenados en nuetro API REST. Para ello, vamos a seguir los lineamientos de Vuex acorde al diagrama anterior y lo ejemplificamos en un diagrama de la siguiente manera:
Flujo de datos para listar ToDo's
- getToDos
En este sentido, necesitamos definir un método que se denomina getToDos que pertenece a un Action de la instancia de Vuex, por lo que tendríamos de la siguiente manera:actions: { getToDos: async function({ commit }){ try { const data = await fetch(server_protocol+'://'+server_ip + ':' + server_port); const todos = await data.json(); commit('fillToDos', todos); } catch(e) { Swal.fire( '¡ERROR!', 'Web Services no encontrado', 'error' ); } } }
Este Action finaliza realizando un commit hacia la Mutation denominada fillToDos cuando el backend responde a la consulta. Caso contrario muestra una alerta de error (usando SweetAlert2).
-
fillToDos
Esta Mutation va a recibir los datos obtenidos por el Action anterior y los va a guardar en el state denominado todos de la siguiente manera:mutations: { fillToDos(state, todos){ state.todos = todos.sort((a, b) => (a.id > b.id)? 1 : -1); } }
-
State: todo
El State, como ya mencionamos, es el que guarda los datos dentro del store de vuex, por lo que lo definimos de la siguiente manera:state: { columnas: ['Título', 'Descripción', 'Acciones'], todos: [], }
Recordemos que hasta ahora todos estos métodos estan definidos dentro de los elementos de la instancia de Vuex.
- Componente: tablatodos
Para finalizar el flujo de datos correspondiente al requerimiento de listar todos los ToDo's de nuestro backend, tenemos que definir un componente de Vue denominado tablatodos que esta siendo llamado en el index.html y que opera con el State llamado todos y columnas para mostrar los datos al usuario. Adicional, hacemos el dispatch al Action: getToDos dentro del método created, de tal manera que: cada vez que el usuario renderice el componente, los datos se actualicen con el backend. El componente se define de la siguiente manera:
Vue.component('tablatodos',{
template: //html
`
<div id="list" class="container mt-5">
<table class="table table-dark mt-5">
<thead>
<tr>
<th scope="col" v-for="columna of columnas">{{columna}}</th>
</tr>
</thead>
<tbody>
<tr v-for="(todo, index) of todos">
<td>{{todo.title}}</td>
<td>{{todo.description}}</td>
<td>
<button class="btn btn-info">EDITAR</button>
<button class="btn btn-danger">ELIMINAR</button>
</td>
</tr>
</tbody>
</table>
<div class="text-center mt-5">
<a class="btn btn-primary" href="nuevo.html">NUEVO TODO</a>
</div>
</div>
`,
computed: {
...Vuex.mapState(['columnas', 'todos'])
},
methods: {
...Vuex.mapActions(['getToDos']),
},
created(){
this.$store.dispatch('getToDos');
}
});
De esta manera ya tenemos listado los ToDo's del backend en forma de tabla dentro de nuestro index.html. Cabe recalcar que un componente de Vue es reactivo, por lo que cualquier cambio que se haga dentro del State: todo de vuex, automáticamente va a ser reflejado dentro del componente tablatodos ya que el mismo hace el llamado adecuado del state.
Listar ToDo's
De la misma forma, han sido definidos los métodos y componentes de cada requerimiento del CRUD de ToDo's dentro del archivo main.js respetando la misma estructura de flujo de datos de Vuex. Puedes revisar el desarrollo completo del API client que esta disponible en: https://github.com/nano-bytes/vuejs/tree/master/python-flask-client.
No te olvides de comentar cualquier duda o recomendación que tengas.
¡Nos veremos en una próxima entrega!