En este tutorial vamos a ver cómo crear una aplicación REST con Vue, el famoso framework de JavaScript. La aplicación soportará las principales operaciones CRUD, que son la lectura, la escritura, el borrado y la actualización de registros.
La aplicación que vamos a crear es muy similar a las que hemos creado en el tutorial de introducción a Vue, con la salvedad de que en este caso usaremos una API externa en lugar de almacenar los datos en nuestro navegador.
Contenidos
Introducción
Como quizás ya sepas, Vue es un framework open source de JavaScript utilizado para crear interfaces de usuario. Vue únicamente se ocupa de la capa de presentación de las aplicaciones, que es lo que ven los usuarios. Lo que vamos a hacer es agregar una interfaz que permita gestionar los datos de los diferentes usuarios que se incluyen en esta API.
Antes de ponernos manos a la obra, deberías tener ciertas consideraciones en cuenta, ya que existen ciertos requisitos básicos que debes cumplir.
Requisitos
Para seguir este tutorial deberías tener ciertos conocimientos de JavaScript, HTML y CSS. Además, es más que recomendable que hayas seguido primero el tutorial de introducción a Vue, especialmente si es la primera vez que usas este framework. A continuación tienes una serie de guías de aprendizaje que te podrían ser de ayuda:
- Aprende HTML: Guía definitiva de HTML
- Aprende JavaScript: Guía definitiva de JavaScript
También deberías tener ciertos conocimientos básicos acerca de cómo usar la terminal de comandos. Usaremos también otras herramientas como Git o el gestor de paquetes npm. Si nunca has usado Git, puedes consultar el tutorial de introducción a Git. Del mismo modo, también puedes puedes consultar el tutorial de introducción a Node.js y npm. De todos modos, verás en todo momento los comandos utilizados, tratándose de meras recomendaciones.
En cuanto a las herramientas a utilizar, personalmente uso VS Code con la extensión Vetur, pero puedes usar cualquier otro IDE, como Sublime con Vue Syntax Highlight o Atom con language-vue.
En cuanto al navegador, puedes usar Chrome o Firefox ya que ambos disponen de diferentes extensiones que te ayudarán durante el desarrollo de la aplicación. Las extensiones más relevantes son las DevTools de Vue.js para Chrome y las DevTools de Vue.js para Firefox.
Objetivos
En este tutorial nos conectaremos a una API REST. Comenzaremos instalando Vue y configurando el servidor de desarrollo. Usaremos una API externa e integraremos nuestra aplicación con ella, conectándonos a los endpoints de lectura, creación, actualización y borrado, que se corresponden con los métodos GET
, POST
, PUT
y DELETE
de la metodología REST.
Además, también haremos el build y el deploy de la aplicación, la cual pondremos en marcha en GitHub pages.A continuación puedes ver el código de la aplicación, así como el resultado final que pretendemos conseguir.
Dicho esto, comencemos.
Instalación de Vue
Si todavía no has instalado Vue en tu sistema, abre una ventana de terminal de comandos e instala Vue CLI con uno de estos dos comandos:
# Instalación con NPM
npm i -g @vue/cli @vue/cli-service-global
# Instalación con Yarn
yarn global add @vue/cli @vue/cli-service-global
Creación del proyecto
Para crear el proyecto, dirígete al directorio en el que quieres crear la aplicación y usa el siguiente comando para crearla:
vue create tutorial-rest-vue
Se te preguntará si prefieres usar Vue 2, Vue 3 o si por el contrario prefieres escoger los componentes a instalar. Selecciona la opción Vue 3, que instalará los componentes por defecto. También podrás seleccionar entre usar NPM o Yarn. En este tutorial usaremos NPM, pero puedes usar el gestor de paquetes que más te guste.
winpty vue.cmd create tutorial-rest-vue
en lugar de vue create tutorial-rest-vue
.# NPM
npm run serve
# Yarn
yarn serve
Luego accede a la URl http://localhost:8080/
o a la que se muestre en tu navegador para ver la página por defecto de Vue en tu navegador.
Dado que no es el objetivo de este tutorial el de aprender CSS, usaremos el framework Bootstrap. Para incluirlo en el proyecto, incluye el siguiente archivo en la sección head
del archivo public/index.html
del proyecto:
<link rel="stylesheet" href="https://stackpath.bootstrapcdn.com/bootstrap/4.5.2/css/bootstrap.min.css" />
Ahora vamos a eliminar el código de ejemplo que se incluye con Vue CLI. Tras esto, abre el archivo App.js
y reemplaza el código que se incluye por defecto por este otro:
<template>
<div id="app" class="container">
<div class="row">
<div class="col-md-12 mt-2">
<h1>Usuarios</h1>
</div>
</div>
</div>
</template>
<script>
export default {
name: 'app',
}
</script>
Sencillamente hemos agregado el título de la página, exportando también la aplicación, que incluimos en el archivo src/index.php
. Una vez tenemos la aplicación configurada, es hora de crear sus componentes.
Comunicación con la API
Usaremos los métodos de esta API, que acepta peticiones de lectura GET
, creación POST
, actualización PUT
y borrado de datos DELETE
. Tal y como ves, la API devuelve datos en formato JSON. Debemos crear los métodos que se comuniquen con la API, encargados de enviar las peticiones a la misma y también de gestionar la respuesta obtenida.
Decir que las peticiones que enviemos no modificarán la lista de usuarios en la base de datos del servidor, ya que se trata de una API de ejemplo. Si no tienes claro lo que es una API, consulta este artículo.
Primero vamos a crear el array que se encargará de almacenar los datos de los usuarios, al que llamaremos usuarios
. Lo crearemos como una variable de estado, incluyéndolo en el método data()
, que agregaremos a la aplicación como parte de su sentencia export default
:
export default {
name: 'app',
data() {
return {
usuarios: [],
}
},
}
A continuación vamos a crear los métodos asíncronos que se conectarán con la API. Por simplificar, usaremos la API Fetch que incorpora JavaScript de forma nativa. Podríamos usar una librería como Axios, pero usaremos la API Fetch por el mero hecho de no requerir dependencias. Los métodos asíncronos que crearemos usarán las sentencias async/await y tendrán esta estructura:
async metodoAsincrono() {
try {
// Obtenemos los datos usando await
const response = await fetch('url');
// Respuesta en formato JSON
const data = await response.json();
// Aquí procesamos los datos
} catch (error) {
// Ejecución en caso de error
}
}
El uso del async
provocará que el código de la aplicación se continúe ejecutando sin esperar al resultado de la función o método que lo incluya. La sentencia await
se usa en el interior de las funciones asíncronas con el objetivo de esperar a que se ejecute una función. En este caso, esperará a que la función fetch
obtenga una respuesta de la API.
Ahora agregaremos los métodos en el componente App.vue
; concretamente en el objeto methods
del mismo. Además, también agregaremos el método mounted
, que se ejecutará cuando se monte el componente, que en este caso ocurre al cargar la aplicación:
export default {
name: 'app',
data() {
return {
usuarios: [],
},
methods: {
getUsuarios() {
// Método para obtener la lista de usuarios
},
postUsuario() {
// Método para crear un usuario
},
putUsuario() {
// Método para actualizar un usuario
},
deleteUsuario() {
// Método para borrar un usuario
},
},
mounted() {
this.getUsuarios();
}
},
}
A continuación vamos a ver en detalle el código de cada método.
GET
Este es el método que usaremos para obtener usuarios, que se ejecutará cuando se monte el componente:
async getUsuarios() {
try {
const response = await fetch('https://jsonplaceholder.typicode.com/users');
this.usuarios = await response.json();
} catch (error) {
console.error(error);
}
}
POST
Este es el método que usaremos para crear usuarios, enviando los datos del usuario en el body
de la petición. Tal y como ves, usamos el método JSON.stringify para transformar el array de usuarios a formato JSON. También usamos el operador spread de propagación para unir el array de usuarios con el objeto que hemos insertado:
async postUsuario(usuario) {
try {
const response = await fetch('https://jsonplaceholder.typicode.com/users', {
method: 'POST',
body: JSON.stringify(usuarios),
headers: { 'Content-type': 'application/json; charset=UTF-8' },
});
const usuarioCreado = await response.json();
this.usuarios = [...this.usuarios, usuarioCreado];
} catch (error) {
console.error(error);
}
}
PUT
Ahora crearemos el método utilizado para actualizar un usuario. Enviamos el id
del usuario que queremos actualizar en la URL, siguiendo así el estándar REST. Enviamos también los datos del usuario actualizado en el body
de la petición:
async putUsuario(usuario) {
try {
const response = await fetch(`https://jsonplaceholder.typicode.com/users/${usuario.id}`, {
method: 'PUT',
body: JSON.stringify(usuario),
headers: { 'Content-type': 'application/json; charset=UTF-8' },
});
const usuarioActualizado = await response.json();
this.usuarios = this.usuarios.map(u => (u.id === usuario.id ? usuarioActualizado : u));
} catch (error) {
console.error(error);
}
}
DELETE
Finalmente crearemos el método encargado de eliminar usuarios:
async deleteUsuario(usuario) {
try {
await fetch(`https://jsonplaceholder.typicode.com/usuarios/${usuario.id}`, {
method: "DELETE"
});
this.usuarios= this.usuarios.filter(u => u.id !== usuario.id);
} catch (error) {
console.error(error);
}
}
Y con esto, ya habríamos agregado todos los métodos necesarios.
Componentes de la aplicación
A continuación vamos a crear los componentes de Vue de la aplicación. Vamos a comenzar mostrando un listado con los usuarios que hemos obtenido de la API.
Componente TablaUsuarios
Mostraremos los usuarios en una tabla, aunque para simplificar, solamente mostraremos su nombre y su email. Para ello, crea un archivo llamado TablaUsuarios.vue
en el directorio src/components
.
Vamos a crear un tabla dinámica que, además de mostrar la lista de usuarios, también incluya un botón para eliminarlos y otro para actualizarlos en la propia tabla.
Creación de la tabla
Comienza agregando el siguiente código, que mostrará los datos de los usuarios que pasaremos como propiedad al componente:
<template>
<div id="tabla-usuarios">
<div v-if="!usuarios.length" class="alert alert-info" role="alert">
No existen usuarios
</div>
<table class="table">
<thead>
<tr>
<th>Nombre</th>
<th>Email</th>
</tr>
</thead>
<tbody>
<tr v-for="usuario in usuarios" :key="usuario.id">
<td>{{ usuario.name }}</td>
<td>{{ usuario.email }}</td>
</tr>
</tbody>
</table>
</div>
</template>
<script>
export default {
name: 'tabla-usuarios',
props: {
usuarios: Array,
},
}
</script>
Lo que hemos hecho en la la sección HTML del componente es crear una tabla y, en ella, usar un bucle v-for
de Vue para recorrer el array de usuarios
, que el componente acepta como una propiedad. Mostramos únicamente dos columnas; una para el nombre «name
» del usuario y otra para su email
.
Además, también usamos una condición v-if
para mostrar una advertencia en caso de que no existan usuarios. Recuerda que la sentencia v-if
solamente mostrará aquel elemento en el que se define si se cumple la condición establecida.
En la sección JavaScript del componente, hemos definido tanto su nombre como el objeto props
, en donde definimos las propiedades que podremos enviar al componente cuando lo rendericemos.
A continuación, vamos a agregar el componente a nuestra aplicación. Para ello edita de nuevo el archivo App.js
e importa el componente en la sección de script
:
import TablaUsuarios from '@/components/TablaUsuarios.vue';
Luego agrega el componente a la lista de componentes:
export default {
// ...
components: {
TablaUsuarios,
},
// ...
}
Seguidamente, renderiza el componente TablaUsuarios
en la aplicación, pasándole el array de usuarios definido como variable de estado en el método data
:
<template>
<div id="app" class="container">
<div class="row">
<div class="col-md-12 mt-2">
<h1>Usuarios</h1>
</div>
</div>
<div class="row">
<div class="col-md-12">
<tabla-usuarios :usuarios="usuarios"/>
</div>
</div>
</div>
</template>
Ahora ya puedes probar a ejecutar la aplicación en tu navegador. Deberías ver por pantalla una tabla con los usuarios que hemos obtenido desde la API:
Sin embargo, todavía tenemos que agregar dos elementos adicionales, que son un botón que nos permita eliminar a los usuarios de la tabla y otro que nos permita mostrar un formulario para actualizar sus datos.
Borrado de filas
Necesitamos agregar una columna más a la tabla en la que agregar el botón de borrado. El botón emitirá el evento eliminar-usuario
a la aplicación, de modo ésta pueda eliminar al usuario tanto del servidor como del array de usuarios. Los eventos se declaran usando la función $emit
, seguido del nombre del evento y de los datos a enviar.
Actualiza el componente TablaUsuarios
con el siguiente código:
<template>
<div id="tabla-usuarios">
<div v-if="!usuarios.length" class="alert alert-info" role="alert">
No se han agregado usuarios
</div>
<table class="table">
<thead>
<tr>
<th>Nombre</th>
<th>Email</th>
<th>Acciones</th>
</tr>
</thead>
<tbody>
<tr v-for="usuario in usuarios" :key="usuario.id">
<td>
{{ usuario.name}}
</td>
<td>
{{ usuario.email}}
</td>
<td>
<button class="btn btn-danger ml-2" @click="$emit('eliminar-usuario', usuario)">🗑️ Borrar</button>
</td>
</tr>
</tbody>
</table>
</div>
</template>
Ahora tenemos que editar el código de la aplicación en el archivo App.js
y declarar la acción que se debe ejecutar cuando se reciba el evento eliminar-usuario
del componente TablaUsuarios
:
<tabla-usuarios :usuarios="usuarios" @eliminar-usuario="deleteUsuario" />
Para declarar la acción hemos usado el atributo @
seguido del nombre del evento, indicando como su valor aquel método que queremos ejecutar, que en este caso es el método deleteUsuario
.
Ahora ya podremos eliminar usuarios. Si abres tu navegador, deberías ver este resultado:
Actualización de filas
A continuación vamos a agregar un botón de edición a la columna de acciones que permita transformar los valores de la fila seleccionada en campos editables. El botón permitirá poner la fila en modo edición. En cuanto a los campos, usaremos la sentencia v-if
para comprobar si estamos en modo edición y, si lo estamos, mostraremos los campos editables.
Cuando estemos en modo edición, mostraremos dos botones diferentes. Un botón servirá para guardar la fila, invocando al método guardarUsuario
. El otro cancelará el modo edición usando el método cancelarEdicion
.
Este es el código HTML final del componente TablaUsuarios
:
<template>
<div id="tabla-usuarios">
<div v-if="!usuarios.length" class="alert alert-info" role="alert">
No se han agregado usuarios
</div>
<table class="table">
<thead>
<tr>
<th>Nombre</th>
<th>Email</th>
<th>Acciones</th>
</tr>
</thead>
<tbody>
<tr v-for="usuario in usuarios" :key="usuario.id">
<td v-if="editando === usuario.id">
<input type="text" class="form-control" v-model="usuario.name" />
</td>
<td v-else>
{{ usuario.name}}
</td>
<td v-if="editando === usuario.id">
<input type="email" class="form-control" v-model="usuario.email" />
</td>
<td v-else>
{{ usuario.email}}
</td>
<td v-if="editando === usuario.id">
<button class="btn btn-success" @click="guardarUsuario(usuario)">💾 Guardar</button>
<button class="btn btn-secondary ml-2" @click="cancelarEdicion(usuario)">❌ Cancelar</button>
</td>
<td v-else>
<button class="btn btn-info" @click="editarUsuario(usuario)">✏️ Editar</button>
<button class="btn btn-danger ml-2" @click="$emit('eliminar-usuario', usuario)">🗑️ Eliminar</button>
</td>
</tr>
</tbody>
</table>
</div>
</template>
Este es el código JavaScript final del componente TablaUsuarios
, en donde, tal y como puedes ver, emitimos el evento actualizar-usuario
cuando se ejecuta el método guardarUsuario
:
export default {
name: 'tabla-usuarios',
props: {
usuarios: Array,
},
data() {
return {
editando: null,
}
},
methods: {
editarUsuario(usuario) {
this.usuarioEditado = Object.assign({}, usuario);
this.editando = usuario.id;
},
guardarUsuario(usuario) {
if (!usuario.name.length || !usuario.email.length) {
return;
}
this.$emit('actualizar-usuario', usuario);
this.editando = null;
},
cancelarEdicion(usuario) {
Object.assign(usuario, this.usuarioEditado);
this.editando = null;
}
}
}
Cuando se ejecuta el método editarUsuario
entramos en modo edición, guardando los datos del usuario que se está editando en la variable de estado usuarioEditado
, que usamos a modo de caché. En caso de cancelar la edición, el método cancelarEdición
restaurará los datos del usuario editado usando esta variable de estado.
La variable editando
se usa a modo de flag, siendo su valor null
si no estamos editando ningún usuario. En caso contrario, contendrá el id
del usuario que se está editando.
A continuación vamos a agregar la acción asociada el evento actualizar-usuario
en el componente App.js
:
<tabla-usuarios :usuarios="usuarios" @eliminar-usuario="deleteUsuario" @actualizar-usuario="putUsuario" />
Este evento invocará al método putUsuario
, actualizando los datos de usuario mediante la API.
Tras estos cambios ya podremos editar usuarios. Si abres tu navegador, deberías ver algo así en tu navegador:
Componente FormularioUsuario
Vamos a agregar un formulario que nos permita crear nuevos usuarios. Para ello, vamos a crear un nuevo componente, así que vamos a crear el archivo FormularioUsuario.vue
en la carpeta src/components
.
Este es el código HTML del componente:
<template>
<div id="formulario-usuario">
<form @submit.prevent="enviarFormulario">
<div class="container">
<div class="row">
<div class="col-md-4">
<div class="form-group">
<label>Nombre</label>
<input
ref="name"
v-model="usuario.name"
type="text"
class="form-control"
:class="{ 'is-invalid': procesando && nameInvalido }"
@focus="resetEstado"
@keypress="resetEstado"
/>
</div>
</div>
<div class="col-md-4">
<div class="form-group">
<label>Email</label>
<input
v-model="usuario.email"
type="email"
:class="{ 'is-invalid': procesando && emailInvalido }"
class="form-control"
@focus="resetEstado"
/>
</div>
</div>
</div>
<div class="row">
<div class="col-md-4">
<div class="form-group">
<button class="btn btn-primary">Añadir usuario</button>
</div>
</div>
</div>
</div>
<div class="container">
<div class="row">
<div class="col-md-12">
<div v-if="error && procesando" class="alert alert-danger" role="alert">
Debes rellenar todos los campos!
</div>
<div v-if="correcto" class="alert alert-success" role="alert">
El usuario ha sido agregado correctamente!
</div>
</div>
</div>
</div>
</form>
</div>
</template>
Lo que hemos hecho es agregar un campo para el nombre del usuario y otro para el email. Mediante el atributo v-model
enlazamos el valor de los campos con el de sus respectivas variables de estado.
Para poder validar los campos, asignaremos la clase CSS is-invalid
a cada uno de los campos en caso de que estén vacíos cuando se envíe el formulario. La clase se eliminará de los campos cuando el foco se sitúe sobre ellos o cuando se pulse una tecla, mediante los eventos @focus
y @keypress
respectivamente, que ejecutarán el método resetEstado
.
También hemos asignado una referencia al campo name
, que nos permitirá situar el foco sobre dicho campo cuando el formulario se envíe exitosamente.
Este sería el código JavaScript completo del campo, que tendrás que agregar entre la etiqueta de apertura <script>
y la de cierre </script>
:
export default {
name: 'formulario-usuario',
data() {
return {
procesando: false,
correcto: false,
error: false,
usuario: {
name: '',
email: '',
}
}
},
methods: {
enviarFormulario() {
this.procesando = true;
this.resetEstado();
// Comprobamos la presencia de errores
if (this.nameInvalido || this.emailInvalido) {
this.error = true;
return;
}
this.$emit('crear-usuario', this.usuario);
this.$refs.name.focus();
this.error = false;
this.correcto = true;
this.procesando = false;
// Restablecemos el valor de la variables
this.usuario= {
name: '',
email: '',
}
},
resetEstado() {
this.correcto = false;
this.error = false;
}
},
computed: {
nameInvalido() {
return this.usuario.name.length < 1;
},
emailInvalido() {
return this.usuario.email.length < 1;
},
},
}
Las propiedades del objeto usuario
están sincronizadas con los datos del formulario. En cuanto a las variables correcto
y error
, las usamos para detectar si el formulario se ha enviado correctamente o si, por el contrario, ha habido algún error.
Hemos agregado los validadores nameInvalido
y emailInvalido
, que sencillamente comprobarán si los campos name
y email
están vacíos. También usamos estos métodos para agregar la clase is-invalid
a los campos del formulario, siempre y cuando se haya producido un error de envío.
Si los datos del formulario son correctos, emitimos el evento crear-usuario
.
A continuación, edita el archivo App.js
e importa el componente FormularioUsuario
:
import FormularioUsuario from '@/components/FormularioUsuario.vue';
Luego agrégalo a la lista de componentes de la aplicación:
// ...
components: {
TablaUsuarios,
FormularioUsuario,
},
// ...
Luego agrega el componente que acabamos de crear justo antes de la tabla, asignado también la acción postUsuario
al evento crear-usuario
:
<formulario-usuario @crear-usuario="postUsuario" />
Y tras esto ya hemos terminado la aplicación. Si ves el resultado el tu navegador, deberías ver la aplicación terminada:
Y con esto, ya hemos terminado la aplicación.
Build & Deploy de la aplicación
Tras finalizar la aplicación, cierra el proceso de Node de la aplicación y ejecuta uno de estos dos comandos para crear una versión de producción:
# NPM
npm run build
#Yarn
yarn build
Si quieres, también puedes publicar la aplicación en algún servicio gratuito de hosting como GitHub Pages o Vercel. Para publicar la aplicación en GitHub Pages, consulta el tutorial en el que explico cómo publicar una aplicación Vue en GitHub Pages.
En este tutorial has aprendido a integrar una aplicación Vue con una API pública. En la muchas ocasiones la API será privada, por lo que necesitarías agregar un sistema de login y de control de sesiones para los usuarios. En futuros tutoriales veremos cómo crear una API en el servidor, que luego podremos integrar con una aplicación Vue.
Y esto ha sido todo.
Una duda, si tengo un fichero clientes.json como el de users que usas en el ejemplo en localhost ¿que hay que modificar para que haga lo mismo? He probado algunas cosas como la ruta en «bruto» pero no lo lee. Gracias
Ya lo he resuelto, instalé json-server y con watch lo cree. Gracias de todas formas