# Galería de imágenes con VueJS y Supabase

## Introducción

En este tutorial, aprenderemos cómo crear una galería de imágenes utilizando dos poderosas herramientas: Vue.js y Supabase.

[Vue.js](https://vuejs.org/) es un framework de JavaScript progresivo que nos permite crear interfaces de usuario interactivas de forma fácil y eficiente. Por otro lado, [Supabase](https://supabase.com/) es una plataforma de desarrollo web que brinda servicios back-end como bases de datos y almacenamiento de archivos, lo que nos permite crear fácilmente aplicaciones web completas.

A lo largo del proceso, aprenderemos a integrar VueJS y Supabase, implementar la carga de archivos y mostrar las imágenes de manera dinámica en nuestra aplicación.

## Proyecto

Nos centraremos en integrar el alojamiento de Supabase en una aplicación Vue.js para crear un formulario de carga de imágenes y una galería que mostrará las imágenes almacenadas en Supabase.

## Consideraciones

Para sacar el mayor provecho de este tutorial, se recomienda cumplir con los siguientes requisitos mínimos:

* Conocimientos básicos de HTML, CSS y JavaScript: necesita conocer los conceptos básicos de HTML para crear la estructura básica de su aplicación web, CSS para diseñarla y JavaScript para agregar interactividad.
    
* Conceptos básicos de Vue.js: es útil tener un conocimiento básico de Vue.js, incluida la creación de componentes, el uso de comandos y la gestión del estado mediante VueX (aunque esto no es necesario en este proyecto, pero puede ser útil en proyectos futuros).
    
* Cuenta de Supabase y conceptos básicos: debe registrarse en Supabase para acceder al alojamiento y otros servicios adicionales. Además, es útil tener una comprensión básica de cómo funciona Supabase y cómo interactuar con él a través de su API.
    

## Creación del proyecto VueJS

Comencemos creando el proyecto VueJS con el siguiente comando:

```plaintext
npm create vite@latest vuejs-supabase -- --template vue
```

Este comando creará un directorio `vuejs-supabase` , entra al directorio e instala las dependencias con `npm install` .

Vamos a usar `bootstrap` para los estilos, lo instalamos con el comando `npm install bootstrap` , luego modificamos el fichero `src/main.js` como sigue:

```javascript
import 'bootstrap/dist/css/bootstrap.css';
import 'bootstrap/dist/js/bootstrap.js';

import './style.css';

import { createApp } from 'vue'
import App from './App.vue'

createApp(App).mount('#app')
```

## Componente Form

Comenzamos creando el componente `src/components/Form.vue` :

```xml
<template>
    <div class="col-3">
        <div class="card">
          <div class="card-body">
            <h2 class="display-5 text-center">Subir Imágen</h2>
            <form>
              <div class="form-group mb-3 text-center">
                <img src="https://fakeimg.pl/200x200/?text=IMG" alt="vista previa" id="preview" class="img-thumbnail">
              </div>
              <div class="form-group mb-3">
                <label for="formFile" class="form-label">Imágen</label>
                <input class="form-control" type="file" id="formFile">
              </div>
              <div class="form-group mb-3">
                <label for="title">Título</label>
                <input type="text" class="form-control" id="title">
              </div>
              <div class="form-group d-grid">
                <button type="submit" class="btn btn-primary">Subir</button>
              </div>
            </form>
          </div>
        </div>
      </div>
</template>
```

Establecemos una sección para el componente del formulario. Definimos una vista previa, un campo de tipo file para el archivo y un campo title para ponerle un título.

Lo importamos al componente principal `src/App.vue` .

```xml
<script setup>
import Form from './components/Form.vue';
</script>

<template>
  <div class="container">
    <div class="row pt-5">
      <Form />
    </div>
  </div>
</template>
```

![](https://cdn.hashnode.com/res/hashnode/image/upload/v1714504617512/3e755994-3b77-452e-8d61-0337d5a9308a.jpeg align="center")

Vamos a crear las funciones para la vista previa y el envío del formulario. En el componente `src/components/Form.vue` añadimos el siguiente código:

```xml
<script setup>
import { ref } from 'vue';


const title = ref('')
const fileInput = ref('')

function handleSubmit() {
  console.log(title.value)
  console.log(fileInput.value.files[0])
  document.querySelector('#preview').src = "https://fakeimg.pl/200x200/?text=IMG";
}

function handleFileChange(event) {
  var file = event.target.files[0];
  var url = URL.createObjectURL(file);
  document.querySelector('#preview').src = url;
};
</script>
```

La función `handleFileChange` se encarga de mostrar la imagen que vamos a subir en la vista previa del formulario.

La función `handleSubmit` por ahora muestra los datos por consola. También declaramos dos variables reactivas `title` y `fileInput` que se encargan de recuperar los datos del formulario que luego vamos a enviar a supabase. Tenemos que vincular a los componentes como sigue:

```xml
<script setup>
import { ref } from 'vue';


const title = ref('')
const fileInput = ref('')

function handleSubmit() {
  console.log(title.value)
  console.log(fileInput.value.files[0])
  document.querySelector('#preview').src = "https://fakeimg.pl/200x200/?text=IMG";
}

function handleFileChange(event) {
  var file = event.target.files[0];
  var url = URL.createObjectURL(file);
  document.querySelector('#preview').src = url;
};
</script>

<template>
    <div class="col-3">
        <div class="card">
          <div class="card-body">
            <h2 class="display-5 text-center">Subir Imagen</h2>
            <!-- Vinculamos la función handleSubmit al evento submit del formulario -->
            <form @submit.prevent="handleSubmit">
              <div class="form-group mb-3 text-center">
                <img src="https://fakeimg.pl/200x200/?text=IMG" alt="vista previa" id="preview" class="img-thumbnail">
              </div>
              <div class="form-group mb-3">
                <label for="formFile" class="form-label">Imágen</label>
                <!-- Vinculamos la función handleFileChange al evento change del input y le añadimos la variable reactiva fileInput -->
                <input class="form-control" type="file" id="formFile" @change="handleFileChange" ref="fileInput">
              </div>
              <div class="form-group mb-3">
                <label for="title">Título</label>
                <!-- Le añadimos la variable reactiva title -->
                <input type="text" class="form-control" id="title" v-model="title">
              </div>
              <div class="form-group d-grid">
                <button type="submit" class="btn btn-primary">Subir</button>
              </div>
            </form>
          </div>
        </div>
      </div>
</template>
```

![](https://cdn.hashnode.com/res/hashnode/image/upload/v1714600389334/34f4cf31-aacc-46bc-b9d7-d50ca115e0ef.gif align="center")

Finalizamos con el formulario, ahora continuamos con la galería que mostrará las imágenes.

## Componente Gallery

Creamos el componente `src/components/Gallery.vue` .

```xml
<template>
    <div class="col-9">
        <h2 class="display-5 text-center p-3">Galería</h2>
        <hr>
        <div class="grid-container">
            <figure class="box-image">
                <img src="https://picsum.photos/600/300" class="image">
                <figcaption class="title-image d-flex justify-content-center align-items-center display-5">Título</figcaption>
            </figure>
            <figure class="box-image">
                <img src="https://picsum.photos/600/300" class="image">
                <figcaption class="title-image d-flex justify-content-center align-items-center display-5">Título</figcaption>
            </figure>
            <figure class="box-image">
                <img src="https://picsum.photos/600/300" class="image">
                <figcaption class="title-image d-flex justify-content-center align-items-center display-5">Título</figcaption>
            </figure>
        </div>
    </div>
</template>
```

Tenemos una grilla de imágenes, dentro de la cuadrícula tenemos un elemento figure que contiene la imagen. Utilizamos una imagen de **picsum.photos** de forma auxiliar por el momento.

Modificamos el fichero `src/style.css` para definir los estilos de la cuadrícula de imágenes.

```css
.grid-container {
  display: grid;
  grid-template-columns: repeat(auto-fit, minmax(300px, 300px));
  gap: 20px;
}

.box-image {
  position: relative;
  width: 100%;
  margin: 0;
  overflow: hidden;
  border-radius: 10px;
  cursor: pointer;
}

.title-image {
  position: absolute;
  top: 300px;
  left: 0;
  width: 100%;
  height: 100%;
  background-color: rgba(0 0 0 / 0.6);
  color: #ffffff;
  transition: 0.3s ease-in-out;
}

.box-image:hover .title-image {
  top: 0px;
}

.image {
  width: 100%;
  object-fit: cover;
}
```

Ahora importamos el componente `src/components/Gallery.vue` justo debajo del componente del formulario.

```xml
<script setup>
import Form from './components/Form.vue';
import Gallery from './components/Gallery.vue';
</script>

<template>
  <div class="container">
    <div class="row pt-5">
      <Form />
      <Gallery />
    </div>
  </div>
</template>
```

Obtenemos la siguiente pantalla.

![](https://cdn.hashnode.com/res/hashnode/image/upload/v1714506711792/9c87e449-45fb-46d9-bfa6-8fbdc346e852.gif align="center")

Ahora los elementos figure que se muestran están deforma estática, vamos a crear un componente para poder mostrarlos de forma dinámica.

## Componente BoxImage

Creamos el componente `src/components/BoxImage.vue` :

```xml
<script setup>
const { img } = defineProps({img: { type: Object, required: true}});
</script>
<template>
    <figure class="box-image">
        <img :src="img.url" class="image">
        <figcaption class="title-image d-flex justify-content-center align-items-center display-5">{{img.title}}</figcaption>
    </figure>
</template>
```

Definimos el `props img` el cuál es un objeto que contiene la url y el título de la imagen. Pasamos la url a la imagen por medio de `<img :src="img.url">` y pasamos el título dentro del figcaption con `{{ img.title }}` . Ahora debemos importar este componente dentro de la galería como sigue:

```xml
<script setup>
import { ref } from 'vue'
import BoxImage from './BoxImage.vue'

const data = ref([
  {
    "id": "0",
    "title": "Polaroid",
    "url": "https://picsum.photos/600/300?image=0"
  },
  {
    "id": "1",
    "title": "Skyscraper",
    "url": "https://picsum.photos/600/300?image=1"
  },
  {
    "id": "2",
    "title": "Sunset",
    "url": "https://picsum.photos/600/300?image=2"
  },
  {
    "id": "3",
    "title": "Nature",
    "url": "https://picsum.photos/600/300?image=3"
  },
  {
    "id": "4",
    "title": "Abstract",
    "url": "https://picsum.photos/600/300?image=4"
  },
  {
    "id": "5",
    "title": "City",
    "url": "https://picsum.photos/600/300?image=5"
  },
  {
    "id": "6",
    "title": "Mountains",
    "url": "https://picsum.photos/600/300?image=6"
  },
  {
    "id": "7",
    "title": "Ocean",
    "url": "https://picsum.photos/600/300?image=7"
  },
  {
    "id": "8",
    "title": "Forest",
    "url": "https://picsum.photos/600/300?image=8"
  },
  {
    "id": "9",
    "title": "Bridge",
    "url": "https://picsum.photos/600/300?image=9"
  }
])
</script>
<template>
    <div class="col-9">
        <h2 class="display-5 text-center p-3">Galería</h2>
        <hr>
        <div class="grid-container">
            <BoxImage v-for="img in data" :key="img.id" :img="img"/>
        </div>
    </div>
</template>
```

Definimos un array de objetos `data` que contiene nuestras imágenes, el cuál iteramos con `<BoxImage v-for"img in data" :key="img.id" :img="img">` , por cada item en data creamos una nueva instancia del componente BoxImage. Con esto obtenemos la siguiente pantalla:

![](https://cdn.hashnode.com/res/hashnode/image/upload/v1715631804903/f3e9dc9a-5961-448e-9df0-4b3966f9f819.gif align="center")

Ya tenemos todo listo en el front-end, ahora pasamos a configurar nuestro proyecto en supabase.

## Supabase

Una vez que hayas ingresado a tu cuenta de Supabase, en el dashboard, crea un nuevo proyecto, yo lo llamé "vuejs-supabase".

![](https://cdn.hashnode.com/res/hashnode/image/upload/v1715801716670/6a9a109e-add2-4ab7-8935-9a30dbd58172.gif align="center")

Una vez finalice la configuración, crea un fichero `.env.local` en la raíz de tu proyecto, haz clic en el botón "connect" en la esquina superior derecha, en la opción de "App Frameworks" elige la "VueJS" y copia las variables de entorno que aparecen en la caja de texto. Son los tokens que te permitirán conectarte al proyecto de Supabase.

![](https://cdn.hashnode.com/res/hashnode/image/upload/v1715801916898/986b51fb-fc07-4b07-af41-50d67a9b44ae.gif align="center")

Debes pegar estas variables de entorno en el fichero `.env.local` . Sin embargo, debemos realizar un ligero cambio. Como creamos nuestro proyecto VueJS con Vite, tenemos que añadir a las variables el prefijo VITE:

```apache

VITE_SUPABASE_URL=https://rbjwpbyyzrsejoomunlr.supabase.co
VITE_SUPABASE_KEY=eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJpc3MiOiJzdXBhYmFzZSIsInJlZiI6InJiandwYnl5enJzZWpvb211bmxyIiwicm9sZSI6ImFub24iLCJpYXQiOjE3MTU3OTg3NzcsImV4cCI6MjAzMTM3NDc3N30.SvR03dwUbR1qrO4duqz7wkJSVjdeVlxvK7t3USd5iDQ
```

Continuamos con la creación de la tabla en la base de datos y el bucket en el storage para las imágenes.

## Tabla Images

Ve a la opción "Table Editor" y haz clic en el botón "Create a new table". Coloca el nombre "images" en el campo Name. Opcionalmente puedes añadir una descripción. Como no vamos a usar autenticación, deshabilitamos la opción "Enable Row Level Security". En la sección Columns añadimos los campos title y url\_image, de tipo VarChar.

![](https://cdn.hashnode.com/res/hashnode/image/upload/v1715885673835/8222580b-2ad9-4af0-b8de-e73d54e74839.gif align="center")

Continuamos con la creación del bucket images para las imágenes.

## Bucket Images

Ve a la opción "Storage", haz clic en el botón "New bucket" y ponle el nombre "images". Establece el bucket como público y guarda. Ahora, debemos establecer las políticas del bucket, que determinan qué tipo de operaciones podemos realizar en el bucket. Haz clic en la opción "Policies", luego en el botón "New policy", elige la opción "For full customization", y selecciona cada una de las operaciones que deseas realizar en el bucket. Finalmente, guarda.

![](https://cdn.hashnode.com/res/hashnode/image/upload/v1715886827101/a17c6bfc-4274-4e1c-b1b8-47d58c3cb2eb.gif align="center")

Ya tenemos todo listo para operar con la base de datos y el bucket. Continuemos con la instalación de las dependencias de Supabase y la función para subir imágenes y guardar los datos en la tabla.

## Instalación de la librería cliente de Supabase

La librería cliente `supabase-js` proporciona una interfaz conveniente para trabajar con Supabase desde una aplicación VueJS. Para instalarla, ejecuta el siguiente comando:

```plaintext
npm install @supabase/supabase-js
```

Continuemos con la configuración.

## Configuración del cliente supabase

Crea un fichero `src/supabase/supabase.js` y añade el siguiente código:

```javascript
import { createClient } from "@supabase/supabase-js";

const supabaseUrl = import.meta.env.VITE_SUPABASE_URL;
const supabaseKey = import.meta.env.VITE_SUPABASE_KEY;

export const supabase = createClient(supabaseUrl, supabaseKey);
```

Este código configura e inicializa un cliente de Supabase. Importa la función `createClient` del paquete `@supabase/supabase-js`. Esta función crea una instancia del cliente de Supabase, que te permitirá interactuar con los servicios de Supabase, como la base de datos, autenticación y almacenamiento. `supabaseUrl` y `supabaseKey` son constantes que almacenan la URL del proyecto de Supabase y la clave de acceso, respectivamente. `import.meta.env` es una forma de acceder a las variables de entorno definidas en archivos de configuración (como `.env.local`) en un proyecto utilizando Vite. `VITE_SUPABASE_URL` y `VITE_SUPABASE_KEY` son las variables de entorno que deben estar definidas en tu archivo `.env.local`. Se utiliza la función `createClient` con `supabaseUrl` y `supabaseKey` para crear una instancia del cliente de Supabase. Esta instancia (`supabase`) se exporta para que pueda ser utilizada en otras partes de la aplicación. Con esta instancia, podrás realizar operaciones como consultar la base de datos, subir archivos, etc.

## Subir imagen y guardar datos en la tabla

Lo primero que debemos hacer es importar la instancia del cliente de supabase en el componente del formulario `src/components/Form.vue` :

```javascript
import { supabase } from '../supabase/supabase';
```

Una vez importamos la instancia, modificamos la función `handleSubmit` como sigue:

```javascript
async function handleSubmit(e) {
  //Obtenemos el fichero y lo subimos al bucket images de nuestro storage
  const file = fileInput.value.files[0];
  await supabase.storage.from('images').upload(file.name, file);
  
  //Obtenemos la url pública de la image que acabamos de subir al bucket
  const { data: { publicUrl } } = await supabase.storage.from('images').getPublicUrl(file.name);
  
  document.querySelector('#preview').src = "https://fakeimg.pl/200x200/?text=IMG";
  
  //Insertamos un nuevo registro en la tabla images con el título y la url pública
  await supabase.from('images').insert([{
    title: title.value,
    url_image: publicUrl,
  },]).select();

  //Reseteamos el formulario y reseteamos las variables reactivas
  e.target.reset();
  title.value = "";
  fileInput.value = "";
          
}
```

Este código define una función `handleSubmit` que gestiona el proceso de subir una imagen a Supabase Storage, obtener su URL pública, insertar un registro en una tabla de base de datos de Supabase con esa URL, y finalmente, resetear el formulario.

`const file = fileInput.value.files[0];` : Se obtiene el primer archivo seleccionado por el usuario desde el input de archivo. `fileInput` es una referencia al elemento de input de tipo `file` en el DOM.

`await supabase.storage.from('images').upload(file.name, file);` : Utiliza el método `upload` del cliente de Supabase Storage para subir el archivo al bucket llamado 'images'. `file.name` es el nombre del archivo y `file` es el objeto del archivo.

`const { data: { publicUrl } } = await supabase.storage.from('images').getPublicUrl(file.name);` : Utiliza el método `getPublicUrl` para obtener la URL pública del archivo subido. `publicUrl` es la URL que permite acceder al archivo de forma pública.

```javascript
javascriptCopiar códigoawait supabase.from('images').insert([{
  title: title.value,
  url_image: publicUrl,
}]).select();
```

Inserta un nuevo registro en la tabla 'images' de la base de datos de Supabase. El registro incluye el título (`title.value`) y la URL de la imagen (`publicUrl`). Se usa `select()` para devolver el registro insertado.

```javascript
e.target.reset();
title.value = "";
fileInput.value = "";
```

Resetea el formulario después de la subida. [`e.target`](http://e.target)`.reset()` limpia todos los campos del formulario. Además, se restablecen manualmente los valores de `title` y `fileInput` a una cadena vacía.

![](https://cdn.hashnode.com/res/hashnode/image/upload/v1716069774805/a7e01394-8063-407f-8bb9-f75904aff70c.gif align="center")

Continuemos con la recuperación de los datos para mostrarlos en la grilla de imágenes.

## Seleccionar registros de la tabla images

Para obtener todos los registros de la tabla images y mostralos en la grilla debemos realizar los siguientes cambios en el componente `src/components/Gallery.vue` :

```javascript
import { onMounted, ref } from 'vue'
import BoxImage from './BoxImage.vue'
import { supabase } from '../supabase/supabase';

const arrayImages = ref([]);

onMounted(async() => {  
  let { data } = await supabase.from('images').select('*');
  arrayImages.value = data;  
          
})
```

Importamos `onMounted`, una función de Vue Composition API que se usa para ejecutar un bloque de código cuando el componente se monta (es decir, se inserta en el DOM).

Renombramos la variable reactiva `data` a un nombre más descriptivo, `arrayImages`.

La línea `let { data } = await supabase.from('images').select('*');` realiza una consulta a la tabla `images` en Supabase para seleccionar todos los registros. La función `supabase.from('images').select('*')` devuelve una promesa que se resuelve con un objeto que contiene los datos (`data`). Luego, asignamos los datos obtenidos de Supabase a `arrayImages.value`. Dado que `arrayImages` es una referencia reactiva, cualquier cambio en su valor provocará una actualización automática en la vista donde se usa `arrayImages`.

Debemos pasar los datos de `arrayImages` al componente `BoxImage.vue` :

```xml
<BoxImage v-for="img in arrayImages" :key="img.id" :img="img"/>
```

Dentro del componente `BoxImage.vue` debemos corregir el valor que le pasamos al atributo dinámico `:src` de la etiqueta `img`, ya que el campo que guarda la url pública lo llamamos `url_image` :

```xml
<template>
    <figure class="box-image">
        <!-- Cambiamos el valor de img.url a img.url_public -->
        <img :src="img.url_image" class="image">
        <figcaption class="title-image d-flex justify-content-center align-items-center display-5">{{img.title}}</figcaption>
    </figure>
</template>
```

Al guardar todos los cambios ya deberías ver la imagen que subimos a Supabase en la grilla.

![](https://cdn.hashnode.com/res/hashnode/image/upload/v1716072540533/1d9a15f3-3a86-48af-8e9a-43b57dcd4983.jpeg align="center")

Sin embargo tenemos un problema, si subimos una nueva imagen, la grilla no se actualiza. Debemos refrescar la página para obtener la nueva imagen.

![](https://cdn.hashnode.com/res/hashnode/image/upload/v1716072863607/17bf38f4-cd7b-49f0-8808-35abfa7abc84.gif align="center")

Para resolver esto debemos configurar un canal de suscripción en Supabase para escuchar cambios en la tabla images, lo que nos permitirá actualizar los datos en la aplicación Vue.js en tiempo real.

## Método Subscribe

Volvemos a Supabase, en el Table Editor, hacemos clic sobre la tabla images y luego en el botón "Realtime off" que está en la esquina superior derecha y lo habilitamos.

![](https://cdn.hashnode.com/res/hashnode/image/upload/v1716073430666/7529ac98-3185-411b-9c48-611adb125ae3.gif align="center")

Ahora volvemos al componente `Gallery.vue` y justo debajo de `arrayImages` creamos la siguiente función:

```javascript
const channels = supabase.channel('custom-insert-channel')
  .on(
    'postgres_changes',
    { event: 'INSERT', schema: 'public', table: 'images' },
    (payload) => {
      arrayImages.value.push(payload.new);
    }
  )
  .subscribe()
```

`supabase.channel('custom-insert-channel')` crea un nuevo canal de Supabase con el nombre `'custom-insert-channel'`. Este canal se utiliza para recibir eventos en tiempo real relacionados con los cambios en la base de datos.

`.on` configura un oyente de eventos para el canal creado. `'postgres_changes'` especifica el tipo de evento a escuchar, que en este caso son los cambios en PostgreSQL. `{ event: 'INSERT', schema: 'public', table: 'images' }` es un objeto que define el filtro para los eventos que queremos escuchar. Aquí estamos interesados en eventos de tipo `INSERT` en la tabla `images` del esquema `public`.

`(payload) => { arrayImages.value.push(payload.new); }` es una función callback que se ejecuta cada vez que ocurre un evento que coincide con el filtro. El `payload` contiene los datos del evento, y `payload.new` contiene el nuevo registro que se ha insertado en la tabla `images`. Dentro del callback, `arrayImages.value.push(payload.new);` agrega el nuevo registro al array reactivo `arrayImages`. Esto asegura que la lista de imágenes en la aplicación Vue.js se actualice automáticamente cuando se inserte una nueva imagen en la base de datos.

![](https://cdn.hashnode.com/res/hashnode/image/upload/v1716074943233/a09c0f5b-ae66-4e65-8c7d-7be0638fe3b4.gif align="center")

Con esto hemos finalizado el tutorial.

## Conclusión

En este tutorial, hemos aprendido cómo integrar Vue.js con Supabase para crear una aplicación web dinámica y reactiva. Hemos cubierto los siguientes aspectos:

1. **Configuración de Supabase y Vue.js:** Desde la instalación de las dependencias hasta la configuración inicial de ambas herramientas.
    
2. **Subida de Imágenes:** Implementamos un formulario para subir imágenes a Supabase Storage y obtener su URL pública.
    
3. **Almacenamiento en Base de Datos:** Guardamos los detalles de las imágenes en una tabla de la base de datos de Supabase.
    
4. **Mostrar Imágenes en una Galería:** Creamos una galería que muestra todas las imágenes almacenadas, recuperándolas de la base de datos.
    
5. **Actualización en Tiempo Real:** Configuramos suscripciones en Supabase para actualizar automáticamente la galería cada vez que se inserta una nueva imagen.
    

Este proyecto no solo muestra las capacidades de Supabase como backend, sino también cómo Vue.js facilita la creación de interfaces de usuario reactivas y eficientes. Al combinar ambas herramientas, pudimos crear una aplicación moderna y funcional con una experiencia de usuario fluida y en tiempo real.

Puedes encontrar el proyecto completo en mi repositorio de GitHub haciendo clic [**AQUÍ**](https://github.com/AlegreCode/vuejs-supabase).

Espero que lo hayas encontrado entretenido, instructivo y claro. Si tienes alguna duda, puedes hacérmelo saber en los comentarios. Pronto estaré subiendo más tutoriales.

Nos vemos en la próxima. Saludos!👋😊
