TechLead
Lección 5 de 8
7 min de lectura
Firebase

Firebase Storage

Almacena y sirve contenido generado por usuarios como imágenes, videos y archivos

¿Qué es Firebase Storage?

Firebase Cloud Storage es un servicio de almacenamiento de objetos potente, simple y rentable construido a escala de Google. Te permite almacenar y servir contenido generado por usuarios, como fotos, videos y otros archivos. Firebase Storage está respaldado por Google Cloud Storage y ofrece cargas y descargas seguras para tus apps de Firebase.

Con Firebase Storage puedes subir archivos directamente desde el cliente sin necesidad de tu propio servidor, y los archivos se almacenan en un bucket de Google Cloud Storage.

📦 Características clave

  • 🔒 Seguro: Las reglas de seguridad protegen tus archivos.
  • 📈 Escalable: Escala automáticamente para cualquier volumen de datos.
  • 🌐 Con CDN: Archivos servidos desde una CDN global para entrega rápida.
  • 📱 Soporte offline: Maneja automáticamente interrupciones de red.
  • ⏸️ Reanudable: Cargas y descargas pueden pausarse y reanudarse.

Configurar Firebase Storage

Inicializa Storage en tu aplicación:

import { initializeApp } from 'firebase/app';
import { getStorage } from 'firebase/storage';

const firebaseConfig = {
  // Your config here
  storageBucket: 'your-project.appspot.com'
};

const app = initializeApp(firebaseConfig);
const storage = getStorage(app);

export { storage };

Estructura de Storage

Los archivos se organizan en una estructura jerárquica similar a un sistema de archivos:

// Storage bucket structure
gs://your-bucket/
├── users/
│   ├── user1/
│   │   ├── avatar.jpg
│   │   └── documents/
│   │       └── resume.pdf
│   └── user2/
│       └── avatar.png
├── posts/
│   ├── post1/
│   │   ├── image1.jpg
│   │   └── image2.jpg
│   └── post2/
│       └── cover.jpg
└── public/
    └── logo.png

Subir archivos

Sube archivos desde diferentes fuentes:

Subir desde input de archivo

import { ref, uploadBytes, getDownloadURL } from 'firebase/storage';

async function uploadFile(file, path) {
  // Create a reference to the file location
  const storageRef = ref(storage, path);

  // Upload the file
  const snapshot = await uploadBytes(storageRef, file);
  console.log('Uploaded file:', snapshot.metadata.name);

  // Get the download URL
  const downloadURL = await getDownloadURL(snapshot.ref);
  return downloadURL;
}

// Usage with file input
const fileInput = document.getElementById('file-input');
fileInput.addEventListener('change', async (e) => {
  const file = e.target.files[0];
  const url = await uploadFile(file, `uploads/${file.name}`);
  console.log('File available at:', url);
});

Subir con seguimiento de progreso

import { ref, uploadBytesResumable, getDownloadURL } from 'firebase/storage';

function uploadFileWithProgress(file, path, onProgress) {
  const storageRef = ref(storage, path);
  const uploadTask = uploadBytesResumable(storageRef, file);

  return new Promise((resolve, reject) => {
    uploadTask.on('state_changed',
      (snapshot) => {
        // Track progress
        const progress = (snapshot.bytesTransferred / snapshot.totalBytes) * 100;
        onProgress(progress);

        switch (snapshot.state) {
          case 'paused':
            console.log('Upload paused');
            break;
          case 'running':
            console.log('Upload running');
            break;
        }
      },
      (error) => {
        // Handle errors
        reject(error);
      },
      async () => {
        // Upload completed
        const downloadURL = await getDownloadURL(uploadTask.snapshot.ref);
        resolve(downloadURL);
      }
    );
  });
}

// Usage
const url = await uploadFileWithProgress(file, 'images/photo.jpg', (progress) => {
  console.log(`Upload is ${progress}% done`);
});

Subir desde string (Base64, Data URL)

import { ref, uploadString, getDownloadURL } from 'firebase/storage';

// Upload base64 encoded string
async function uploadBase64(base64String, path) {
  const storageRef = ref(storage, path);
  const snapshot = await uploadString(storageRef, base64String, 'base64');
  return getDownloadURL(snapshot.ref);
}

// Upload data URL (e.g., from canvas)
async function uploadDataURL(dataURL, path) {
  const storageRef = ref(storage, path);
  const snapshot = await uploadString(storageRef, dataURL, 'data_url');
  return getDownloadURL(snapshot.ref);
}

// Upload from canvas
const canvas = document.getElementById('myCanvas');
const dataURL = canvas.toDataURL('image/png');
const url = await uploadDataURL(dataURL, 'images/drawing.png');

Agregar metadatos

Adjunta metadatos personalizados a los archivos:

import { ref, uploadBytes, updateMetadata } from 'firebase/storage';

// Upload with metadata
async function uploadWithMetadata(file, path, customMetadata) {
  const storageRef = ref(storage, path);

  const metadata = {
    contentType: file.type,
    customMetadata: {
      uploadedBy: customMetadata.userId,
      description: customMetadata.description,
      originalName: file.name
    }
  };

  const snapshot = await uploadBytes(storageRef, file, metadata);
  return snapshot;
}

// Update metadata after upload
async function updateFileMetadata(path, newMetadata) {
  const storageRef = ref(storage, path);

  const updatedMetadata = await updateMetadata(storageRef, {
    customMetadata: newMetadata
  });

  return updatedMetadata;
}

Descargar archivos

Obtén URLs de descarga y descarga archivos:

import { ref, getDownloadURL, getBlob, getBytes } from 'firebase/storage';

// Get download URL
async function getFileURL(path) {
  const storageRef = ref(storage, path);
  const url = await getDownloadURL(storageRef);
  return url;
}

// Download as Blob (for files up to memory limit)
async function downloadAsBlob(path) {
  const storageRef = ref(storage, path);
  const blob = await getBlob(storageRef);
  return blob;
}

// Download as bytes
async function downloadAsBytes(path) {
  const storageRef = ref(storage, path);
  const bytes = await getBytes(storageRef);
  return bytes;
}

// Usage: Display image
const url = await getFileURL('images/photo.jpg');
document.getElementById('image').src = url;

Eliminar archivos

Elimina archivos del almacenamiento:

import { ref, deleteObject } from 'firebase/storage';

async function deleteFile(path) {
  const storageRef = ref(storage, path);

  try {
    await deleteObject(storageRef);
    console.log('File deleted successfully');
  } catch (error) {
    if (error.code === 'storage/object-not-found') {
      console.log('File does not exist');
    } else {
      throw error;
    }
  }
}

// Delete user's avatar
await deleteFile('users/user123/avatar.jpg');

Listar archivos

Lista archivos en un directorio:

import { ref, listAll, list } from 'firebase/storage';

// List all files in a folder
async function listAllFiles(path) {
  const storageRef = ref(storage, path);
  const result = await listAll(storageRef);

  // Process items (files)
  const files = await Promise.all(
    result.items.map(async (item) => ({
      name: item.name,
      fullPath: item.fullPath,
      url: await getDownloadURL(item)
    }))
  );

  // Process prefixes (folders)
  const folders = result.prefixes.map((folder) => ({
    name: folder.name,
    fullPath: folder.fullPath
  }));

  return { files, folders };
}

// Paginated listing (for large directories)
async function listFilesPaginated(path, maxResults = 10, pageToken = null) {
  const storageRef = ref(storage, path);

  const options = { maxResults };
  if (pageToken) {
    options.pageToken = pageToken;
  }

  const result = await list(storageRef, options);

  return {
    files: result.items,
    folders: result.prefixes,
    nextPageToken: result.nextPageToken // Use for next page
  };
}

Componente de carga de archivos en React

Crea un componente reutilizable para subir archivos:

import { useState } from 'react';
import { ref, uploadBytesResumable, getDownloadURL } from 'firebase/storage';
import { storage } from '../lib/firebase';

export function FileUpload({ onUploadComplete, path = 'uploads' }) {
  const [progress, setProgress] = useState(0);
  const [uploading, setUploading] = useState(false);
  const [error, setError] = useState(null);

  const handleFileChange = async (e) => {
    const file = e.target.files[0];
    if (!file) return;

    setUploading(true);
    setError(null);

    const fileName = `${Date.now()}-${file.name}`;
    const storageRef = ref(storage, `${path}/${fileName}`);
    const uploadTask = uploadBytesResumable(storageRef, file);

    uploadTask.on('state_changed',
      (snapshot) => {
        const progress = (snapshot.bytesTransferred / snapshot.totalBytes) * 100;
        setProgress(progress);
      },
      (error) => {
        setError(error.message);
        setUploading(false);
      },
      async () => {
        const downloadURL = await getDownloadURL(uploadTask.snapshot.ref);
        setUploading(false);
        setProgress(0);
        onUploadComplete({ url: downloadURL, name: fileName });
      }
    );
  };

  return (
    <div>
      <input type="file" onChange={handleFileChange} disabled={uploading} />
      {uploading && (
        <div>
          <progress value={progress} max="100" />
          <span>{Math.round(progress)}%</span>
        </div>
      )}
      {error && <p className="text-red-500">{error}</p>}
    </div>
  );
}

// Usage
function ProfilePage() {
  const handleUpload = ({ url, name }) => {
    console.log('Uploaded:', url);
    // Save URL to user profile in database
  };

  return <FileUpload onUploadComplete={handleUpload} path="avatars" />;
}

Redimensionar imagen antes de subir

Redimensiona imágenes en el cliente antes de subirlas:

async function resizeImage(file, maxWidth, maxHeight) {
  return new Promise((resolve) => {
    const reader = new FileReader();
    reader.onload = (e) => {
      const img = new Image();
      img.onload = () => {
        const canvas = document.createElement('canvas');
        let width = img.width;
        let height = img.height;

        // Calculate new dimensions
        if (width > height) {
          if (width > maxWidth) {
            height *= maxWidth / width;
            width = maxWidth;
          }
        } else {
          if (height > maxHeight) {
            width *= maxHeight / height;
            height = maxHeight;
          }
        }

        canvas.width = width;
        canvas.height = height;

        const ctx = canvas.getContext('2d');
        ctx.drawImage(img, 0, 0, width, height);

        canvas.toBlob(resolve, 'image/jpeg', 0.8);
      };
      img.src = e.target.result;
    };
    reader.readAsDataURL(file);
  });
}

// Usage
async function uploadResizedImage(file, path) {
  const resizedBlob = await resizeImage(file, 800, 600);
  const storageRef = ref(storage, path);
  const snapshot = await uploadBytes(storageRef, resizedBlob);
  return getDownloadURL(snapshot.ref);
}

💡 Puntos clave

  • • Firebase Storage guarda archivos en buckets de Google Cloud Storage
  • • Usa uploadBytes para subidas simples y uploadBytesResumable para progreso
  • • Obtén URLs con getDownloadURL para servir archivos
  • • Agrega metadatos personalizados para organizar y buscar archivos
  • • Redimensiona imágenes en cliente para ahorrar ancho de banda y almacenamiento
  • • Usa reglas de seguridad para proteger el acceso a archivos

📚 Más recursos

Continuar Aprendiendo