En este artículo te voy a enseñar a consumir una API REST usando JavaScript y el framework Vue.js, además de utilizar Bootstrap para el diseño.
Al final vamos a tener una aplicación web que va a gestionar datos y hacer las 4 operaciones básicas: crear, actualizar, eliminar y obtener.
Todo esto lo vamos a hacer sin tocar el código del servidor, pues simplemente nos vamos a enfocar en la programación del lado del cliente, así que solo veremos HTML, JavaScript y un poco de CSS.
Sobre la API
Esta API REST va a implementar las operaciones fundamentales y los 4 verbos HTTP. Además, va a utilizar JSON para el intercambio de datos, así que al enviar datos los debemos enviar como JSON, y al recibirlos debemos decodificarlos.
La API que vamos a consumir es la que he creado anteriormente con Flask y Python, realmente esto no importa, pues eso es lo bueno de las APIS: que no importa el lenguaje de programación o la base de datos en las que estén implementadas.
Recuerda que esta API REST gestiona juegos o videojuegos, y expone las siguientes rutas:
- GET /games – Obtener todos los juegos
- GET /game/{id} – Obtener un juego por ID
- POST /game – Agregar un juego
- PUT /game – Actualizar un juego
- DELETE /game/{id} – Eliminar un juego por ID
Verás que es realmente sencillo consumir la API con fetch, JavaScript y Vue.
Quiero aclarar algo. En este caso la API devuelve cada juego como un arreglo y no como un objeto, no afecta en nada, pero quiero que quede claro. Así que por ejemplo en lugar de acceder a juego.nombre
, vamos a acceder a juego[1]
.
POST – Crear juego
Comencemos viendo la operación POST para crear un nuevo juego usando esta API. Veamos el diseño HTML:
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Get games - Consuming API with Vue</title>
<link rel="stylesheet" href="css/bootstrap.min.css">
<link rel="stylesheet" href="css/all.min.css">
<link rel="stylesheet" href="css/style.css">
</head>
<nav class="navbar navbar-expand-md navbar-dark bg-success fixed-top">
<a class="navbar-brand" href="https://parzibyte.me/blog">
<img class="img-fluid" style="max-height: 50px" src="img/parzibyte_logo.png" loading="lazy">
</a>
<button class="navbar-toggler" type="button" data-toggle="collapse" id="botonMenu"
aria-label="Mostrar u ocultar menú">
<span class="navbar-toggler-icon"></span>
</button>
<div class="collapse navbar-collapse" id="menu">
<ul class="navbar-nav mr-auto">
<li class="nav-item">
<a class="nav-link" href="get.html">Games <i class="fa fa-gamepad"></i></a>
</li>
<li class="nav-item">
<a class="nav-link" href="create.html">New Game <i class="fa fa-plus-circle"></i></a>
</li>
<li class="nav-item active">
<a class="nav-link" href="https://parzibyte.me/l/fW8zGd">Support & help <i
class="fa fa-hands-helping"></i></a>
</li>
</ul>
</div>
</nav>
<body>
<div id="app" class="container-fluid">
<div class="row">
<div class="col-12">
<h2>New game</h2>
<div class="form-group">
<label for="name">Name:</label>
<input id="name" v-model="game.name" placeholder="Name" type="text" class="form-control">
</div>
<div class="form-group">
<label for="price">Price:</label>
<input id="price" v-model="game.price" placeholder="Price" type="number" class="form-control">
</div>
<div class="form-group">
<label for="rate">Rate:</label>
<input id="rate" v-model="game.rate" placeholder="Rate" type="number" class="form-control">
</div>
<div class="form-group">
<button @click="save()" class="btn btn-success">Save</button>
<a href="get.html" class="btn btn-info">Go back</a>
</div>
</div>
</div>
</div>
<footer class="px-2 py-2 fixed-bottom bg-dark">
<span class="text-muted">REST API consume with Vue and JavaScript by
<a class="text-white" href="//parzibyte.me/blog">Parzibyte</a>
|
<a target="_blank" class="text-white" href="https://parzibyte.me/blog/">
View source
</a>
</span>
</footer>
</body>
<script src="js/menu.js"></script>
<script src="js/vue.min.js"></script>
<script src="js/vue-toasted.min.js"></script>
<script src="js/create.js"></script>
</html>
Lo importante aquí es el div que tiene el id app
, ya que en ese elemento es en donde va a estar nuestra app de Vue. También fíjate en el formulario y sobre todo en los campos que tienen relacionado cada campo del objeto game
(juego) con v-model
.
Finalmente fíjate en el botón, cuyo clic va a invocar al método save
. Ahora pasemos a ver el código JavaScript que básicamente recogerá los valores y los va a enviar a la API.
const SERVER_URL = "https://sitio.com";
Vue.use(Toasted);
new Vue({
el: "#app",
data: () => ({
game: {
name: "",
price: "",
rate: "",
},
}),
methods: {
async save() {
if (!this.game.name) {
return this.$toasted.show("Please write name", {
position: "top-left",
duration: 1000,
});
}
if (!this.game.price) {
return this.$toasted.show("Please write price", {
position: "top-left",
duration: 1000,
});
}
if (!this.game.rate) {
return this.$toasted.show("Please write rate", {
position: "top-left",
duration: 1000,
});
}
const payload = JSON.stringify(this.game);
const url = SERVER_URL + "/game";
const r = await fetch(url, {
method: "POST",
body: payload,
headers: {
"Content-type": "application/json",
}
});
const response = await r.json();
if (response) {
this.$toasted.show("Saved", {
position: "top-left",
duration: 1000,
});
this.game = {
name: "",
price: null,
rate: null,
};
} else {
this.$toasted.show("Something went wrong. Try again", {
position: "top-left",
duration: 1000,
});
}
}
}
});
Comencemos viendo a data
, tiene definido el objeto game
(juego) con todas sus propiedades vacías. Este objeto es el que está ligado a los campos a través del v-model
, y dentro de la app de Vue nos referimos al mismo usando this.game
.
Ahora veamos al método save
, en este caso se hace una pequeña validación en donde se comprueba cada campo y en caso de que esté vacío, se detiene la función y se muestra un Toast indicando que cierto campo no está lleno.
Luego en la línea 33 creamos el payload que será simplemente el juego codificado como JSON, mismo que enviamos en la línea 36 usando fetch. Es importante indicar el método que en este caso es POST, y el tipo de contenido que es application/json
.
Dependiendo de lo que el servidor responda (true
o false
) mostramos un Toast indicando lo sucedido, y limpiamos el formulario simplemente reiniciando los valores del juego.
Obtener datos con GET – Listado de juegos
Pasando al siguiente apartado, veamos cómo se hace una petición GET para obtener un arreglo de datos y dibujarlos en una tabla. Pero vamos paso por paso. Primero hacemos el diseño, para hacerlo simple solo pondré el código relevante:
<table class="table">
<thead>
<tr>
<th>ID</th>
<th>Name</th>
<th>Price</th>
<th>Rate</th>
<th>Edit</th>
<th>Delete</th>
</tr>
</thead>
<tbody>
<tr v-for="game in games">
<td>{{game[0]}}</td>
<td>{{game[1]}}</td>
<td>{{game[2] | currency}}</td>
<td>{{game[3]}}</td>
<td>
<button @click="edit(game)" class="btn btn-warning">
<i class="fa fa-edit"></i>
</button>
</td>
<td>
<button @click="deleteGame(game)" class="btn btn-danger">
<i class="fa fa-trash"></i>
</button>
</td>
</tr>
</tbody>
</table>
La tabla está maquetada normalmente con una tabla HTML, pero ahora veamos el cuerpo de la misma. En este caso tenemos un tr
que es un table row o fila de tabla que se va a repetir usando v-for por cada juego que exista.
Después tenemos varias td
que son celdas de esa fila y que van a mostrar los datos de cada juego iterado. En este caso, como lo mencioné anteriormente, accedemos a game[0]
o en la posición requerida, en lugar de acceder a la propiedad como si de un objeto se tratase.
También es interesante ver los botones que tenemos en la línea 19 y 24, pues respectivamente van a invocar a edit
(pasando como argumento al juego) y a deleteGame
.
Antes de pasar al código JavaScript quiero que veas el filtro de dinero (currency en este caso) en la línea 16.
Pasemos al código de JavaScript. Primero, con el filtro que simplemente le agrega dos decimales y el signo de pesos:
Vue.filter("currency", value => {
return "$".concat(parseFloat(value).toFixed(2));
});
Ahora veamos toda la app de Vue. Comenzando con la data, que únicamente tiene la propiedad de juegos que al inicio es un arreglo vacío:
data: () => ({
games: [],
}),
En el método mounted
de la app estamos obteniendo los juegos, el método se ve así:
async getGames() {
const url = SERVER_URL + "/games";
const r = await fetch(url);
const games = await r.json();
this.games = games;
},
Hacemos una petición GET a la URL que devuelve los juegos y después asignamos el resultado a this.games
, haciendo que Vue note ese cambio y dibuje los juegos en la tabla.
Aquí también tenemos al método de editar que simplemente hace una redirección a otra página que veremos más adelante:
edit(game) {
window.location.href = "./edit.html?id=" + game[0];
},
HTTP DELETE – Eliminar juego
Ya que estamos viendo a la obtención de videojuegos en la tabla, veamos qué sucede cuando se hace clic en el botón de eliminar. Lo que se hace es mostrar una alerta de confirmación usando SweetAlert, y en caso de que el usuario confirme, se hace la petición.
Recuerda que la petición debe ser de tipo DELETE y debemos enviar el ID del juego en la URL:
async deleteGame(game) {
const result = await Swal.fire({
title: 'Delete',
text: "Are you sure you want to delete this game?",
icon: 'question',
showCancelButton: true,
confirmButtonColor: '#1f9bcf',
cancelButtonColor: '#d9534f',
cancelButtonText: 'No',
confirmButtonText: 'Yes, delete it'
});
// Stop if user did not confirm
if (!result.value) {
return;
}
const r = await fetch(SERVER_URL + "/game/" + game[0], {
method: "DELETE",
});
const response = await r.json();
if (response) {
this.$toasted.show("Deleted", {
position: "top-left",
duration: 1000,
});
await this.getGames();
} else {
this.$toasted.show("Something went wrong. Try again", {
position: "top-left",
duration: 1000,
});
}
}
Como en otros casos, mostramos un toast dependiendo de la respuesta. En caso de que todo vaya bien, refrescamos la lista de juegos (línea 25) para que se vea que el juego realmente se ha eliminado en tiempo real.
Petición HTTP PUT con Vue
Para terminar este tutorial veamos cómo hacer la operación UPDATE o mejor dicho cómo actualizar un valor en esta API REST.
Aquí vamos a hacer dos llamadas a la API. La primera será para obtener los detalles del juego (método GET) y la segunda será cuando se haga la actualización con PUT.
Veamos el diseño HTML que básicamente es como el formulario para agregar un nuevo juego:
<div id="app" class="container-fluid">
<div class="row">
<div class="col-12">
<h2>Edit game</h2>
<div class="form-group">
<label for="name">Name:</label>
<input id="name" v-model="game.name" placeholder="Name" type="text" class="form-control">
</div>
<div class="form-group">
<label for="price">Price:</label>
<input id="price" v-model="game.price" placeholder="Price" type="number" class="form-control">
</div>
<div class="form-group">
<label for="rate">Rate:</label>
<input id="rate" v-model="game.rate" placeholder="Rate" type="number" class="form-control">
</div>
<div class="form-group">
<button @click="save()" class="btn btn-success">Save</button>
<a href="get.html" class="btn btn-info">Go back</a>
</div>
</div>
</div>
</div>
La diferencia es que ahora los campos ya van a estar llenos cuando el usuario quiera enviarlos. Primero tenemos que obtener el valor de la URL:
async getGameDetails() {
const urlSearchParams = new URLSearchParams(window.location.search);
const id = urlSearchParams.get("id");
const r = await fetch(`${SERVER_URL}/game/${id}`);
const game = await r.json();
this.game = {
id: game[0],
name: game[1],
price: game[2],
rate: game[3],
};
},
En la línea 3 extraemos el id de la URL. Luego consultamos a la API para obtener un juego usando ese ID. Finalmente en la línea 6 modificamos la variable game
asignando cada valor de lo que nos devolvió la API.
Finalmente cuando el usuario hace clic en guardar se desencadena lo siguiente:
async save() {
if (!this.game.name) {
return this.$toasted.show("Please write name", {
position: "top-left",
duration: 1000,
});
}
if (!this.game.price) {
return this.$toasted.show("Please write price", {
position: "top-left",
duration: 1000,
});
}
if (!this.game.rate) {
return this.$toasted.show("Please write rate", {
position: "top-left",
duration: 1000,
});
}
const payload = JSON.stringify(this.game);
const url = SERVER_URL + "/game";
const r = await fetch(url, {
method: "PUT",
body: payload,
headers: {
"Content-type": "application/json",
}
});
const response = await r.json();
if (response) {
window.location.href = "./get.html";
} else {
this.$toasted.show("Something went wrong. Try again", {
position: "top-left",
duration: 1000,
});
}
}
La petición se está haciendo en la línea 23, enviamos el juego (que ya debe llevar el id) codificado como JSON e indicamos que el método será PUT. En caso de que todo vaya bien, hacemos una redirección al listado de todos los juegos.
Poniendo todo junto
A lo largo del post te he mostrado el código más relevante para consumir una API usando JavaScript y Vue. En este caso la API fue programada con Python, Flask y SQLite 3 pero al final eso no nos importó, ya que mientras las rutas hayan estado bien definidas nuestra app web funcionó perfectamente.
El código completo lo dejo en GitHub.
Más adelante traeré más tutoriales de consumo de APIs usando otros lenguajes y frameworks. Mientras tanto te dejo con más contenido sobre JavaScript y Vue.
Te agradezco muchísimo este post. Están todos los elementos para aprender la interacción de todas estas tecnologías en una sola pasada. Es fantástico y anda al pelo!!. Buenísima contribución!! Mil gracias!!
Muchas gracias por sus comentarios. Me motivan a traer más contenido.
Le envío un saludo y le deseo éxito.