¡Clase en Vue 3 para transformar tus endpoints! ????

«

Es correcto, lo entiendo, tal vez el título te parezca un poco llamativo pero confía en mí, te simplificará la vida como no te imaginas, y te mostraré cómo hacer esto con unos cuantos archivos. Para este ejemplo utilizaremos al increíble 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 estás familiarizado con ello, no te preocupes, puedes usar yarn, npm u otro sin problema alguno. Ahora compartiré mi configuración para que puedas usar lo que elegí en este proyecto nuevo

Después ejecutamos los comandos que nos indique la terminal que estemos utilizando

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

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

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

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

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

src/main.ts

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 servicios 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;

Lo sé, lo sé, estarás preguntándote ¿Qué es todo esto? ¿Qué quiere que añada a mi código, este individuo? Bueno, te lo voy a explicar.

Hemos creado una clase muy útil para nuestro proyecto de vue llamada APIBase, piensa en esto como un puente entre tú y el servicio web, facilitando cualquier tipo de envío y 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 indicar al servicio que elimine algún dato (DELETE). Es como un control con botones para diferentes acciones.

¿Y qué sucede con las direcciones web y los encabezados?

Aquí ocurre algo súper 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, como Content-Type  o si tienes permiso para realizar 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 considero es crucial aclarar, ¿Y si algo sale mal, qué?

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

En palabras sencillas, 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 como para un principiante. Ok, ahora que tienes claro qué hace la clase, vamos a utilizarla para que veas la magia suceder. En esta ocasión utilizaré una api pública que está en jsonplaceholder.typicode.com, crearemos una carpeta llamada Data y dentro de esta crearemos un archivo llamado genericData.ts y aquí escribiremos 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 hermosa.

Ahora por ú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í, observa

const dataStore

En Grupo MET podemos ayudarte a implementar esta y muchas mas herramienta para optimizar tu trabajo. ¡Contáctanos para saber más!

Contactanos