El conocimiento es el nuevo dinero.
Aprender es la nueva manera en la que inviertes
Acceso Cursos

¡Transforma tus endpoints con esta increíble clase en Vue 3! 🌟

· 9 min de lectura
¡Transforma tus endpoints con esta increíble clase en Vue 3! 🌟

Es cierto, lo sé, tal vez el título te parezca un poco clickbait pero créeme que te facilitará la vida como no tienes idea, y te enseñaré como hacer esto con unos pocos archivos. Para este ejemplo usaremos obviamente al hermoso de vue, y empezamos con lo siguiente en tu terminal de preferencia.

pnpm create vue@latest

Si eres nuevo y ves que uso pnpm y no tienes idea qué es, tranquilo, no hay problema cual uses, así sea yarn, npm o alguno de esos. Ahora te dejaré mi configuración para que uses lo que escogí en este proyecto nuevo

Luego corremos los comandos que nos indica la terminal que estés usando

cd super-endpoint/ && pnpm install && pnpm dev
terminal

Podrás visualizar que el proyecto ya esta corriendo, una vez aquí, buscamos esa dirección que en mi caso es http://localhost:5173/. Ahora si, dejaremos esto por un rato y abriremos nuestro proyecto en visual studio code ó tu editor de preferencia.

Cuando lo abras ve a tu App.vue, aquí borrarás todo, y escribirás solo lo siguiente

<template>
  <h1>
    estoy aprendiendo a usar endpoints como un profesional
  </h1>
</template>
app.vue

El siguiente paso es ir al archivo src/main.ts ó src/main.js y borrarás la importación de los estilos, una vez eliminado sigue adelante, porque nos enfocaremos en cómo usar los endpoints

src/main.ts
CPU
1 vCPU
MEMORIA
1 GB
ALMACENAMIENTO
10 GB
TRANSFERENCIA
1 TB
PRECIO
$ 4 mes
Para obtener el servidor GRATIS debes de escribir el cupon "LEIFER"

Ahora puedes ir al navegador al sitio que te mencioné anteriormente localhost:5173, y verás lo siguiente. Si lo tienes como en la imagen de abajo super biennnn, ahora empezaremos con el código.

browser

Listo, volveremos a nuestro editor de código y crearemos una carpeta en /src/ llamada services y aquí adentro crearemos un archivo llamado Base.ts  y aquí dentro escribirás lo siguiente

import axios from 'axios';
import type { AxiosResponse } from 'axios';

class APIBase {
  private baseUrl: string;
  constructor() {
    this.baseUrl = ‘AQUI-SI-O-SI-DEBE-IR-TU-API || 'http://localhost:8000/api';
  }
  private buildUrl(endpoint: string): string {
    return `${this.baseUrl}/${endpoint}`;
  }
  private getHeaders(): { [key: string]: string } {
    const headers: { [key: string]: string } = {
      'Content-Type': 'application/json'
    }
    const accessToken = localStorage.getItem('access_token');
    if (accessToken) {
      headers['Authorization'] = `Bearer ${accessToken}`
    }
    return headers;
  }
  protected async get<T>(endpoint: string): Promise<T> {
    const url = this.buildUrl(endpoint);
    try {
      const response: AxiosResponse<T> = await axios.get(url, {
        headers: this.getHeaders()
      });
      return response.data;
    } catch (error: any) {
      const errorDetails = {
        status: error.response.status,
        message: error.response?.data?.message || error.message
      }
      throw errorDetails;
    }
  }
  protected async post<T>(endpoint: string, data?: any): Promise<T> {
    const url = this.buildUrl(endpoint);
    try {
      const response: AxiosResponse<T> = await axios.post(url, data, {
        headers: this.getHeaders()
      });
      return response.data;
    } catch (error: any) {
      const errorDetails = {
        status: error.response.status,
        message: error.response?.data?.message || error.message
      }
      throw errorDetails;
    }
  }
  protected async postWithFormData<T>(endpoint: string, formData: FormData): Promise<T> {
    const url = this.buildUrl(endpoint);
    try {
      const headers = this.getHeaders();
      headers['Content-type'] = 'multipart/form-data';
      const response: AxiosResponse<T> = await axios.post(url, formData, { headers });
      return response.data;
    } catch (error: any) {
      const errorDetails = {
        status: error.response.status,
        message: error.response?.data?.message || error.message
      }
      throw errorDetails;
    }
  }
  protected async put<T>(endpoint: string, data: any): Promise<T> {
    const url = this.buildUrl(endpoint);
    try {
      const response: AxiosResponse<T> = await axios.put(url, data, {
        headers: this.getHeaders()
      });
      return response.data;
    } catch (error: any) {
      const errorDetails = {
        status: error.response.status,
        message: error.response?.data?.message || error.message
      }
      throw errorDetails;
    }
  }
  protected async patch<T>(endpoint: string, data: any): Promise<T> {
    const url = this.buildUrl(endpoint);
    try {
      const response: AxiosResponse<T> = await axios.patch(url, data, {
        headers: this.getHeaders()
      });
      return response.data;
    } catch (error: any) {
      const errorDetails = {
        status: error.response.status,
        message: error.response?.data?.message || error.message
      }
      throw errorDetails;
    }
  }
  protected async delete<T>(endpoint: string): Promise<T> {
    const url = this.buildUrl(endpoint);
    try {
      const response: AxiosResponse<T> = await axios.delete(url, {
        headers: this.getHeaders()
      });
      return response.data;
    } catch (error: any) {
      const errorDetails = {
        status: error.response.status,
        message: error.response?.data?.message || error.message
      }
      throw errorDetails;
    }
  }
}
export default APIBase;

Yo se, yo se, te estarás preguntando ¿Qué es todo esto? ¿Qué quiere que agregue a mi código, este degenerado? Bueno, te lo voy a explicar.

Creamos una clase muy útil para nuestro proyecto de vue llamada APIBase, mira esto como una especie de puente entre tú y el servicio web, facilitando cualquier tipo de envío y la recepción de información.

Pero ¿cómo se comunica APIBase?

Bueno, esto utiliza unos métodos geniales para hacer cosas básicas que usamos con frecuencia, como pedir datos(GET), enviar datos (POST), actualizar datos (PUT y PATCH) o incluso decirle al servicio que borre algún dato (DELETE). Es como un control con botones para diferentes acciones.

¿Y que hay de las direcciones web y los encabezados?

Aquí pasa algo super interesante, resulta que APIBase, tiene un poder llamado buildUrl. Es lo que estará a cargo de crear las urls, luego tiene otro poder llamado getHeaders, esto establece etiquetas super importantes para tu petición, tales como Content-Type  o si tienes permiso para hacer la petición que sería el access_token, la clase se encarga de eso para que la petición sepa que eres alguien de confianza.

Y la última pregunta que creo es vital aclarar, ¿Y si algo sale mal, qué?

Esto es lo bello de la clase, esta también piensa en esto. Si por alguna razón las peticiones con el servicio web se complica y algo falla, no te dejará abandonado sin saber que ocurre. Agarrará lo que salió mal y te lo explicará, diciéndote que ocurrió y por qué.

En palabras simples, esta clase es como tu intermediario personal con el mundo de los servicios en línea. Hace que hablar con APIs Rest sea fácil, tanto para alguien con experiencia hasta un principiante.Ok, ahora que tienes claro que hace la clase, vamos a usarla para que veas la magia ocurrir. En esta ocasión usaré una api pública que está en jsonplaceholder.typicode.com, haremos una carpeta llamada Data y dentro de esta crearemos un archivo llamado genericData.ts y aquí escribiremos el cómo se utilizan los endpoints, en mi caso son así:

import APIBase from "../Base"; 
class GenericData extends APIBase {
  constructor() {
    super();
  }
  
  async getPosts(): Promise<any> {
    return this.get('posts?_limit=10');
  }
  
  async getPostById(id: string): Promise<any> {
    return this.get(`posts/${id}`);
  }
  
  async createPost(postData: { title: string, body: string, userId: number }): Promise<any> {
    return this.post('posts', postData);
  }
  
  async updatePost(id: string, postData: { title: string, body: string }): Promise<any> {
    return this.put(`posts/${id}`, postData);
  }
  
  async deletePost(id: string): Promise<any> {
    return this.delete(`posts/${id}`);
  }
}

export default GenericData;
src/services/Data/genericData.ts

Una vez después de hacer el siguiente paso es instalar pinia, de la siguiente manera en tu terminal

pnpm add pinia

Justo después de esto te irás a tu main.ts, e inicializamos pinia, solo copia y pega lo que voy a escribir

import { createApp } from ‘vue’
import App from ‘./App.vue’
import { createPinia } from ‘pinia’

const app = createApp(App)

const pinia = createPinia() 
app.use(pinia)

app.mount(‘#app’)
src/main.ts

Ahora crearemos otra carpeta más llamada store, y aquí dentro crearemos otro archivo llamado genericDataStore.ts  y escribiremos dentro lo siguiente

import { defineStore } from 'pinia';
import GenericData from '@/services/Data/genericData';

const genericDataService = new GenericData(); 

interface RootState {
  posts: any[],
  errorMessage: string | null,
  isLoading: boolean,
}
src/store/genericDataStore.ts

Esto será el tipado que respetará nuestro estado en este archivo, después escribirás lo siguiente

export const useGenericDataStore = defineStore('genericDataStore', {
  state: (): RootState => ({
    posts: [],
    errorMessage: null,
    isLoading: false,
  }),
  
  actions: {
    async fetchPosts(): Promise<void> {
      this.isLoading = true;
      try {
        const response = await genericDataService.getPosts();
        this.posts = response;
      } catch (error: any) {
        this.errorMessage = 'Hubo un problema al obtener los posts';
        throw error;
      } finally {
        this.isLoading = false;
      }
    },
    
    async fetchPostById(id: string): Promise<any> {
      this.isLoading = true;
      try {
        const response = await genericDataService.getPostById(id);
        return response;
      } catch (error: any) {
        this.errorMessage = 'Hubo un problema al obtener el post';
        throw error;
      } finally {
        this.isLoading = false;
      }
    },
    
    async createPost(postData: { title: string, body: string, userId: number }): Promise<void> {
      this.isLoading = true;
      try {
        await genericDataService.createPost(postData);
      } catch (error: any) {
        this.errorMessage = 'Hubo un problema al crear el post';
        throw error;
      } finally {
        this.isLoading = false;
      }
    },
    
    async updatePost(id: string, postData: { title: string, body: string }): Promise<void> {
      this.isLoading = true;
      try {
        await genericDataService.updatePost(id, postData);
      } catch (error: any) {
        this.errorMessage = 'Hubo un problema al actualizar el post';
        throw error;
      } finally {
        this.isLoading = false;
      }
    },
    
    async deletePost(id: string): Promise<void> {
      this.isLoading = true;
      try {
        await genericDataService.deletePost(id);
      } catch (error: any) {
        this.errorMessage = 'Hubo un problema al eliminar el post';
        throw error;
      } finally {
        this.isLoading = false;
      }
    },
  },
});
export default useGenericDataStore;

Esto es lo que se encargará de establecer los endpoints del archivo anterior y una vez consumido se guardará en el estado y la forma de manipularlo en los archivos .vue será simplemente bello.

Ahora último, ¿cómo realmente es el consumo de este estado?

Bueno, pues, nos iremos a nuestro app.vue, y harás lo siguiente

Primero importarás lo que usaremos, es decir lo siguiente en mi caso

import useGenericDataStore from '@/store/genericDataStore';

Ahora inicializaremos el estado, ¿cómo lo haremos? Así, mira

const dataStore = useGenericDataStore();

Y luego seguirán algunos computed

const posts = computed(() => dataStore.posts);
const isLoading = computed(() => dataStore.isLoading);

Y por último el onMounted

onMounted(async () => {
  try {
    await dataStore.fetchPosts(); 
  } catch (error) {
    console.error('error getting posts: ', error);
  }
});

El script completo se vería así

<script setup>
import { computed, onMounted } from 'vue';
import useGenericDataStore from '@/store/genericDataStore';

const dataStore = useGenericDataStore();

const posts = computed(() => dataStore.posts);
const isLoading = computed(() => dataStore.isLoading);

onMounted(async () => {
  try {
    await dataStore.fetchPosts(); 
  } catch (error) {
    console.error('error getting posts: ', error);
  }
});
</script>

El template será el siguiente

<template>
  <div>
    <h1>Estoy aprendiendo a usar endpoints como un profesional</h1>
    <div v-if="isLoading">
      Cargando posts...
    </div>
    <div v-else>
      <div v-for="post in posts" :key="post.id">
        <h3>{{ post.title }}</h3>
        <p>{{ post.body }}</p>
      </div>
    </div>
  </div>
</template>

Ufffff, llegamos al final, sí que escribí bastante y en tú caso leíste y espero esto te ayude a mejorar como programador. Con todo esto que hiciste aunque un poco largo créeme que te solucionará la vida muchísimo al momento de usar varios endpoints a lo largo de tu proyecto. Ahora iremos al navegador, y visualizarás lo siguiente

browser

Sé que obviamente le faltan estilos pero lo bonito de esto es que esta información no está en nuestro proyecto, hicimos una llamada a un api, y es más, lo hicimos como verdaderos profesionales de esta gigantesca industria.


By: yeyodev, Dev In Progress.