Cambiar tema

Primeros pasos con Supabase: backend integral con PostgreSQL + Auth + Storage

Easton editorial illustration: one application card connected to three backend modules

Mirando el duodécimo error de conexión a la base de datos en pantalla, quedó claro: para un desarrollador frontend, montar algo full stack es difícil.

Antes, cada función de backend implicaba aprender Node.js, Express, configurar la base de datos, la autenticación y el almacenamiento de archivos — cada pieza, un agujero distinto. Hasta que apareció Supabase.

En pocas palabras, es una alternativa open source a Firebase, pero con PostgreSQL en lugar de un NoSQL que da dolores de cabeza. Además empaqueta base de datos, autenticación y almacenamiento en un solo backend.

Este artículo te lleva desde cero con Supabase, centrado en Database, Auth y Storage. Al terminar deberías poder montar un backend completo sin pelearte con archivos de configuración.


Qué es Supabase

Empecemos por lo básico: qué es Supabase.

Es una plataforma BaaS, Backend as a Service — backend como servicio. Es decir, no montas servidores, no configuras la base de datos ni escribes APIs: ellos lo resuelven por ti.

A diferencia de Firebase, Supabase es totalmente open source. Y usa PostgreSQL, lo cual importa: es relacional, permite SQL complejo y relaciones de datos claras — mucho más práctico que Firestore documental.

Supabase tiene seis funciones principales:

  • Database: base PostgreSQL con consultas SQL y REST API autogenerada
  • Auth: autenticación con email, login social (Google, GitHub, etc.) y JWT
  • Storage: almacenamiento de archivos, similar a AWS S3 pero más simple
  • Realtime: sincronización en tiempo real, ideal para chat
  • Edge Functions: computación en el edge, similar a AWS Lambda
  • Vector Database: base vectorial para aplicaciones de IA

En este artículo nos centramos en las tres primeras: Database, Auth y Storage — el núcleo. El resto lo veremos más adelante.

Seguro te preguntas: open source, PostgreSQL, auth y storage… ¿mejor que Firebase? Lo detallamos después; en resumen: si necesitas SQL, relaciones complejas o control total de los datos, elige Supabase; si haces apps móviles con sincronización en tiempo real muy exigente, Firebase puede encajar mejor.


Primeros pasos

Crear un proyecto Supabase

Ve a supabase.com y regístrate. Luego pulsa «New Project» para crear un proyecto.

Al crearlo rellenarás:

  • Nombre del proyecto: el que quieras, por ejemplo my-first-app
  • Contraseña de la base de datos: anótala, la usarás después
  • Región: la más cercana; desde China, Singapore o Tokyo suelen ir bien

En unos tres minutos estará listo. Verás el panel con el Dashboard y un menú a la izquierda: Table Editor, Authentication, Storage, Edge Functions… parece mucho, pero en este artículo lo iremos viendo.

Instalación e inicialización

Instala Supabase en tu proyecto frontend. Primero la dependencia:

npm install @supabase/supabase-js

Luego busca en el Dashboard de Supabase el Project URL y la Anon Public Key, en Settings > API.

Inicializa el cliente:

import { createClient } from '@supabase/supabase-js';

const supabaseUrl = 'https://your-project-id.supabase.co';
const supabaseAnonKey = 'your-anon-public-key';

export const supabase = createClient(supabaseUrl, supabaseAnonKey);

Este código es la puerta de entrada a Supabase. Consultas, login y subida de archivos pasan por el objeto supabase.

Probar la conexión

Tras inicializar, comprueba que conecta. Lo más simple es consultar la base:

const { data, error } = await supabase.from('users').select('*');

if (error) {
  console.error('Conexión fallida:', error.message);
} else {
  console.log('¡Conexión correcta!', data);
}

Si dice que no existe la tabla users, es normal — aún no la has creado. En la siguiente sección lo vemos.

Errores habituales:

  • URL o Key mal copiados: revisa que estén completos
  • Error CORS: a veces hay que configurarlo en el frontend, aunque Supabase suele permitirlo por defecto
  • Red: ocasionalmente falla; espera unos minutos y reintenta

Si todo va bien verás datos — quizá un array vacío [] si la tabla aún no tiene filas.


Database: operaciones con la base de datos

Aquí está el corazón de Supabase. Si conoces PostgreSQL te resultará familiar; si vienes del frontend y SQL te cuesta, el Table Editor visual te permite operar sin escribir SQL.

Crear tablas

Hay dos formas: Table Editor (visual) o SQL.

En el Dashboard, Table Editor → «Create a new table». Rellena nombre, campos y tipos.

Ejemplo, tabla users:

CampoTipoRestricciones
idint8Primary Key, Auto Increment
emailtextUnique, Not Null
nametext-
created_attimestamptzDefault: now()

Pulsa «Save» y listo.

Para operaciones complejas, SQL suele ir más rápido. El SQL Editor está en el menú izquierdo del Dashboard.

SQL para users y projects:

-- Tabla de usuarios
CREATE TABLE users (
  id SERIAL PRIMARY KEY,
  email TEXT UNIQUE NOT NULL,
  name TEXT,
  created_at TIMESTAMPTZ DEFAULT NOW()
);

-- Tabla de proyectos
CREATE TABLE projects (
  id SERIAL PRIMARY KEY,
  user_id INTEGER REFERENCES users(id),
  title TEXT NOT NULL,
  description TEXT,
  status TEXT DEFAULT 'active',
  created_at TIMESTAMPTZ DEFAULT NOW()
);

Detalle: user_id en projects es clave foránea de users.id. Cada proyecto pertenece a un usuario — ventaja de lo relacional.

Operaciones CRUD

Con las tablas creadas, prueba crear, leer, actualizar y borrar.

Insertar datos:

// Insertar un usuario
const { data, error } = await supabase
  .from('users')
  .insert([
    { email: '[email protected]', name: 'John Doe' }
  ]);

if (error) {
  console.error('Inserción fallida:', error.message);
} else {
  console.log('Inserción correcta:', data);
}

Consultar datos:

// Consultar proyectos e incluir datos del usuario
const { data, error } = await supabase
  .from('projects')
  .select(`
    *,
    users (
      name,
      email
    )
  `)
  .eq('status', 'active')
  .order('created_at', { ascending: false });

console.log(data);

Una sola consulta trae projects y el usuario relacionado por la clave foránea. En Firestore harían falta varias consultas y unir datos a mano.

Actualizar datos:

const { data, error } = await supabase
  .from('projects')
  .update({ status: 'completed' })
  .eq('id', 1);

Eliminar datos:

const { data, error } = await supabase
  .from('projects')
  .delete()
  .eq('id', 1);

Son operaciones directas. .eq() filtra «igual a»; también hay .gt(), .lt(), .like() — filtros SQL habituales.

Row Level Security (RLS)

Importante cuando trabajas con datos de usuarios.

RLS, Row Level Security: controlas que cada usuario solo vea lo suyo.

Ejemplo: en projects quieres que tras el login solo aparezcan los proyectos del usuario actual.

Activa RLS:

ALTER TABLE projects ENABLE ROW LEVEL SECURITY;

Crea una política:

-- El usuario solo ve sus proyectos
CREATE POLICY "Users can view their own projects"
ON projects FOR SELECT
USING (user_id = auth.uid());

auth.uid() devuelve el ID del usuario autenticado. La política limita las filas visibles a las que coinciden con ese ID.

Políticas de inserción, actualización y borrado:

-- Solo insertar proyectos propios
CREATE POLICY "Users can insert their own projects"
ON projects FOR INSERT
WITH CHECK (user_id = auth.uid());

-- Solo actualizar proyectos propios
CREATE POLICY "Users can update their own projects"
ON projects FOR UPDATE
USING (user_id = auth.uid())
WITH CHECK (user_id = auth.uid());

-- Solo eliminar proyectos propios
CREATE POLICY "Users can delete their own projects"
ON projects FOR DELETE
USING (user_id = auth.uid());

Aunque alguien intente consultar datos ajenos, la base los bloquea — más seguro que comprobar solo en el frontend.


Auth: sistema de autenticación

Supabase Auth cubre muchos métodos: email/contraseña, Magic Link sin contraseña, OTP, login social (Google, GitHub, Apple y más de 20 plataformas), teléfono y SSO empresarial.

Panorama de Auth

Mecanismo central: JWT (JSON Web Token). Tras el login, Supabase emite un token; el frontend lo envía en las peticiones y el backend valida la identidad.

Los usuarios se guardan en auth.users de PostgreSQL — tabla automática, sin que la crees tú. ID, email, fechas de creación y último acceso, etc.

También hay persistencia de sesión: el navegador recuerda el login y no hace falta volver a autenticarse en cada visita. El SDK frontend de Supabase lo gestiona solo.

Autenticación Email/Password

Registro:

// Registro de usuario
const { data, error } = await supabase.auth.signUp({
  email: '[email protected]',
  password: 'securepassword123',
});

if (error) {
  console.error('Registro fallido:', error.message);
} else {
  console.log('Registro correcto:', data);
}

Tras registrarse, Supabase envía un correo de confirmación; al pulsar el enlace se activa la cuenta. Puedes desactivarlo en el Dashboard, pero confirmar el email reduce registros abusivos.

Inicio de sesión:

// Inicio de sesión
const { data, error } = await supabase.auth.signInWithPassword({
  email: '[email protected]',
  password: 'securepassword123',
});

if (error) {
  console.error('Inicio de sesión fallido:', error.message);
} else {
  console.log('Inicio de sesión correcto:', data);
}

Usuario actual:

// Usuario autenticado
const { data: { user } } = await supabase.auth.getUser();

console.log('Usuario actual:', user);
// Ejemplo: { id: 'abc123', email: '[email protected]', ... }

Cierre de sesión:

await supabase.auth.signOut();

La sesión se borra y hay que volver a iniciar sesión.

Restablecer contraseña:

// Enviar correo de restablecimiento
const { data, error } = await supabase.auth.resetPasswordForEmail(
  '[email protected]'
);

El usuario recibe el enlace y define una contraseña nueva sin lógica extra tuya.

Social Auth

Login con Google o GitHub sin formularios largos mejora la experiencia.

En el Dashboard: Authentication > Providers, activa Google, GitHub, etc.

Pasos típicos:

  1. Crear OAuth App en Google/GitHub
  2. Copiar Client ID y Client Secret a Supabase
  3. Configurar la URL de callback (Redirect URL)

Código frontend:

// Login con Google
const { data, error } = await supabase.auth.signInWithOAuth({
  provider: 'google',
});

// Login con GitHub
const { data, error } = await supabase.auth.signInWithOAuth({
  provider: 'github',
});

Redirige a la pantalla de autorización; al aceptar, vuelve a tu app ya autenticado.

Parámetros opcionales:

const { data, error } = await supabase.auth.signInWithOAuth({
  provider: 'google',
  options: {
    redirectTo: 'https://your-app.com/dashboard',
    queryParams: {
      access_type: 'offline',
      prompt: 'consent',
    }
  }
});

redirectTo es la página tras el login; queryParams son parámetros OAuth extra.

RLS junto con Auth

Con auth.uid() en políticas conectas login y permisos.

Al crear un proyecto quieres asociarlo al usuario actual:

// Crear proyecto vinculado al usuario actual
const { data: { user } } = await supabase.auth.getUser();

const { data, error } = await supabase
  .from('projects')
  .insert([
    {
      title: 'New Project',
      user_id: user.id  // ID desde Auth
    }
  ]);

RLS garantiza:

  • Al consultar, solo filas con user_id = auth.uid()
  • Al insertar, user_id debe ser auth.uid()
  • Igual para actualizar y borrar

Autenticación, datos y permisos quedan alineados.


Storage: almacenamiento de archivos

Supabase Storage es almacenamiento de objetos, parecido a AWS S3 pero más sencillo. Sirve para avatares, imágenes y documentos.

Conceptos de Storage

Dos ideas clave: Bucket y Object.

  • Bucket: contenedor, como una carpeta. Puedes tener avatars, documents, etc.
  • Object: el archivo concreto, p. ej. avatar.jpg, report.pdf

Modos de bucket:

  • Public: cualquiera accede (imágenes públicas)
  • Private: solo con autorización (documentos privados)

Crear un bucket

En el Dashboard, Storage → «New Bucket».

Ejemplo avatars:

  • Nombre: avatars
  • Public bucket: marcado (avatares suelen ser públicos)
  • File size limit: p. ej. 2 MB

Ya puedes subir archivos.

Subir y descargar archivos

Subida:

// Subir avatar
const file = document.getElementById('avatar-input').files[0];

const { data, error } = await supabase.storage
  .from('avatars')
  .upload('user-id/avatar.jpg', file, {
    cacheControl: '3600',
    upsert: false
  });

if (error) {
  console.error('Subida fallida:', error.message);
} else {
  console.log('Subida correcta:', data);
}

Detalles:

  • Primer argumento de upload(): ruta 'user-id/avatar.jpg'
  • cacheControl: caché en segundos (3600)
  • upsert: si existe, false falla; true sobrescribe

Descarga:

// Descargar archivo
const { data, error } = await supabase.storage
  .from('avatars')
  .download('user-id/avatar.jpg');

if (error) {
  console.error('Descarga fallida:', error.message);
} else {
  // data es un Blob; conviértelo a URL para mostrarlo
  const url = URL.createObjectURL(data);
  document.getElementById('avatar-img').src = url;
}

Bucket público — URL directa:

// URL pública
const { data } = supabase.storage
  .from('avatars')
  .getPublicUrl('user-id/avatar.jpg');

console.log('URL pública:', data.publicUrl);
// Ejemplo: https://your-project.supabase.co/storage/v1/object/public/avatars/user-id/avatar.jpg

Bucket privado — URL firmada temporal:

// URL temporal (válida 1 hora)
const { data, error } = await supabase.storage
  .from('documents')
  .createSignedUrl('private-file.pdf', 3600);

console.log('URL temporal:', data.signedUrl);

Eliminar:

const { data, error } = await supabase.storage
  .from('avatars')
  .remove(['user-id/avatar.jpg']);

Control de acceso

Storage también usa políticas RLS.

Ejemplo: solo la carpeta del usuario.

-- Subir solo a la carpeta propia
CREATE POLICY 'Users can upload to their own folder'
ON storage.objects FOR INSERT
WITH CHECK (
  bucket_id = 'avatars' AND
  (storage.foldername(name))[1] = auth.uid()::text
);

-- Acceder solo a archivos de la carpeta propia
CREATE POLICY 'Users can access their own files'
ON storage.objects FOR SELECT
USING (
  bucket_id = 'avatars' AND
  (storage.foldername(name))[1] = auth.uid()::text
);

storage.foldername(name) extrae la carpeta del path; [1] es el primer segmento (ID de usuario).

La ruta debe ser user-id/filename; RLS comprueba que user-id coincida con auth.uid().


Caso práctico: app de gestión de tareas

Un ejemplo que une todo.

App sencilla con:

  • Registro e inicio de sesión
  • Crear y listar tareas
  • Subir adjuntos

Diseño de la base de datos

-- Tabla de usuarios (Auth la crea sola)
-- Tabla tasks
CREATE TABLE tasks (
  id SERIAL PRIMARY KEY,
  user_id UUID REFERENCES auth.users(id) NOT NULL,
  title TEXT NOT NULL,
  description TEXT,
  status TEXT DEFAULT 'pending' CHECK (status IN ('pending', 'completed')),
  created_at TIMESTAMPTZ DEFAULT NOW(),
  updated_at TIMESTAMPTZ DEFAULT NOW()
);

-- Activar RLS
ALTER TABLE tasks ENABLE ROW LEVEL SECURITY;

-- Política: solo las tareas propias
CREATE POLICY 'Users can manage their own tasks'
ON tasks FOR ALL
USING (user_id = auth.uid())
WITH CHECK (user_id = auth.uid());

-- Tabla task_attachments
CREATE TABLE task_attachments (
  id SERIAL PRIMARY KEY,
  task_id INTEGER REFERENCES tasks(id) ON DELETE CASCADE,
  file_name TEXT NOT NULL,
  file_path TEXT NOT NULL,
  file_size INTEGER,
  created_at TIMESTAMPTZ DEFAULT NOW()
);

-- Activar RLS
ALTER TABLE task_attachments ENABLE ROW LEVEL SECURITY;

-- Política vía la tarea relacionada
CREATE POLICY 'Users can manage attachments of their tasks'
ON task_attachments FOR ALL
USING (
  EXISTS (
    SELECT 1 FROM tasks
    WHERE tasks.id = task_attachments.task_id
    AND tasks.user_id = auth.uid()
  )
);

En task_attachments el permiso pasa por task_idtasks.user_id, no por un user_id directo en adjuntos.

Crear bucket de Storage

Bucket privado task-files:

-- En el Dashboard o con SQL
INSERT INTO storage.buckets (name, public)
VALUES ('task-files', false);

-- Subir solo a la carpeta propia
CREATE POLICY 'Users can upload task files'
ON storage.objects FOR INSERT
WITH CHECK (
  bucket_id = 'task-files' AND
  (storage.foldername(name))[1] = auth.uid()::text
);

CREATE POLICY 'Users can access task files'
ON storage.objects FOR SELECT
USING (
  bucket_id = 'task-files' AND
  (storage.foldername(name))[1] = auth.uid()::text
);

Código principal

import { createClient } from '@supabase/supabase-js';

const supabase = createClient(
  'https://your-project.supabase.co',
  'your-anon-key'
);

// 1. Registro
async function register(email: string, password: string) {
  const { data, error } = await supabase.auth.signUp({
    email,
    password,
  });

  if (error) throw error;
  return data;
}

// 2. Inicio de sesión
async function login(email: string, password: string) {
  const { data, error } = await supabase.auth.signInWithPassword({
    email,
    password,
  });

  if (error) throw error;
  return data;
}

// 3. Crear tarea
async function createTask(title: string, description?: string) {
  const { data: { user } } = await supabase.auth.getUser();

  if (!user) throw new Error('No has iniciado sesión');

  const { data, error } = await supabase
    .from('tasks')
    .insert([
      {
        title,
        description,
        user_id: user.id,
      }
    ])
    .select();

  if (error) throw error;
  return data[0];
}

// 4. Listar tareas
async function getTasks() {
  const { data, error } = await supabase
    .from('tasks')
    .select('*')
    .order('created_at', { ascending: false });

  if (error) throw error;
  return data;
}

// 5. Subir adjunto
async function uploadAttachment(taskId: number, file: File) {
  const { data: { user } } = await supabase.auth.getUser();

  if (!user) throw new Error('No has iniciado sesión');

  const filePath = user.id + '/' + taskId + '/' + file.name;

  const { data, error } = await supabase.storage
    .from('task-files')
    .upload(filePath, file);

  if (error) throw error;

  // Guardar en task_attachments
  const { data: attachment, error: dbError } = await supabase
    .from('task_attachments')
    .insert([
      {
        task_id: taskId,
        file_name: file.name,
        file_path: filePath,
        file_size: file.size,
      }
    ])
    .select();

  if (dbError) throw dbError;

  return attachment[0];
}

// 6. Obtener adjuntos
async function getTaskAttachments(taskId: number) {
  const { data, error } = await supabase
    .from('task_attachments')
    .select('*')
    .eq('task_id', taskId);

  if (error) throw error;

  // URLs firmadas temporales
  const attachmentsWithURLs = data.map(async (attachment) => {
    const { data: urlData } = await supabase.storage
      .from('task-files')
      .createSignedUrl(attachment.file_path, 3600);

    return {
      ...attachment,
      url: urlData.signedUrl,
    };
  });

  return Promise.all(attachmentsWithURLs);
}

// 7. Completar tarea
async function completeTask(taskId: number) {
  const { data, error } = await supabase
    .from('tasks')
    .update({ status: 'completed', updated_at: new Date() })
    .eq('id', taskId)
    .select();

  if (error) throw error;
  return data[0];
}

// 8. Cerrar sesión
async function logout() {
  await supabase.auth.signOut();
}

Registro, login, CRUD de tareas y archivos — el esqueleto de una app completa. Sobre esto puedes añadir categorías, etiquetas, comentarios o notificaciones.


Supabase vs Firebase

¿Cuál elegir? Comparación directa.

Diferencias principales

AspectoSupabaseFirebase
Tipo de base de datosPostgreSQL (relacional)Firestore (NoSQL documental)
Open sourceTotalmente open sourceProducto cerrado de Google
Modelo de preciosFijo 25 $/mes (Pro Plan)Por uso (lectura/escritura/almacenamiento)
Propiedad de datos100 % tuyos, exportación libreDatos en Google, exportación difícil
ConsultasSQL potente y relaciones complejasConsultas limitadas, joins costosos
Tiempo realWebSocket, suscripción manualTiempo real nativo en Firestore
OfflineCaché por tu cuentaSoporte offline nativo
MigraciónPostgreSQL estándar, sencillaFormato propietario, coste alto

Cuándo usar cada uno

Elige Supabase si:

  • Relaciones complejas y SQL
  • Proyecto a largo plazo y datos bajo control
  • El equipo domina SQL y PostgreSQL
  • Presupuesto ajustado (coste fijo más predecible)
  • Prioridad open source

Elige Firebase si:

  • Prototipo rápido con poco tiempo
  • App móvil con offline crítico
  • Sincronización en tiempo real central (chat, etc.)
  • Ya usas Google Cloud
  • Datos simples y pocas consultas complejas

En proyectos anteriores con Firebase la factura llegó a más de 800 $/mes; con Supabase, misma funcionalidad, unos 25 $ estables. La diferencia es grande.

Exportar de Firestore es incómodo — formato propietario y conversión extra. Con Supabase exportas SQL o CSV con herramientas PostgreSQL.

Firebase sigue fuerte en tiempo real y móvil: sync y offline de Firestore están muy pulidos. Para chat o documentos colaborativos puede ser más cómodo.


Resumen y recomendaciones

Valor central de Supabase:

  • PostgreSQL — consultas potentes y relaciones claras
  • Auth de nivel empresarial — varios métodos de login, JWT, RLS
  • Almacenamiento de objetos — simple y con control de acceso
  • Realtime y Edge Functions — para explorar después

Es open source, datos controlables y coste estable — importante en proyectos largos.

Si eres frontend y quieres full stack sin pelearte con la configuración del backend, Supabase encaja bien: en unas horas puedes tener base de datos, auth y storage — mucho más rápido que un backend clásico.

Recomendaciones de aprendizaje:

  • Domina primero Database, Auth y Storage
  • Prueba políticas RLS para ver cómo encajan auth y permisos
  • Aprende con un proyecto real de principio a fin
  • Después explora Realtime, Edge Functions y Vector Database

La documentación oficial está clara: supabase.com/docs. En la comunidad hay tutoriales y listas como awesome-supabase en GitHub.

En una frase: si quieres PostgreSQL como backend sin montar toda la infraestructura, Supabase ya te ha abierto el camino — solo tienes que avanzar.


Primeros pasos con las tres funciones principales de Supabase

Crea un proyecto Supabase desde cero y domina Database, Auth y Storage

⏱️ Estimated time: 2 hr

  1. 1

    Step 1: Crear un proyecto Supabase

    Ve a supabase.com, regístrate y crea un proyecto nuevo:

    • Nombre del proyecto: my-first-app
    • Contraseña de la base de datos: anótala bien
    • Región: la más cercana (desde China, Singapore o Tokyo)

    Al terminar, obtén Project URL y Anon Public Key en Settings > API
  2. 2

    Step 2: Instalar e inicializar el cliente

    Instala la dependencia en tu proyecto frontend:

    npm install @supabase/supabase-js

    Inicializa el cliente Supabase:

    import { createClient } from '@supabase/supabase-js';

    const supabase = createClient(
    'https://your-project.supabase.co',
    'your-anon-key'
    );
  3. 3

    Step 3: Crear tablas y configurar RLS

    Usa SQL Editor para crear tablas:

    CREATE TABLE tasks (
    id SERIAL PRIMARY KEY,
    user_id UUID REFERENCES auth.users(id),
    title TEXT NOT NULL,
    status TEXT DEFAULT 'pending'
    );

    ALTER TABLE tasks ENABLE ROW LEVEL SECURITY;

    CREATE POLICY 'Users can manage their own tasks'
    ON tasks FOR ALL
    USING (user_id = auth.uid());

    La política RLS garantiza que cada usuario solo acceda a sus propios datos
  4. 4

    Step 4: Implementar autenticación de usuarios

    Ejemplos de autenticación Email/Password:

    // Registro
    await supabase.auth.signUp({
    email: '[email protected]',
    password: 'password123'
    });

    // Inicio de sesión
    await supabase.auth.signInWithPassword({
    email: '[email protected]',
    password: 'password123'
    });

    // Obtener usuario actual
    const { data: { user } } = await supabase.auth.getUser();

    Soporta inicio de sesión social con Google, GitHub y más de 20 plataformas
  5. 5

    Step 5: Configurar almacenamiento de archivos

    Crea un bucket de Storage (público o privado):

    // Subir archivo
    await supabase.storage
    .from('avatars')
    .upload('user-id/avatar.jpg', file);

    // Obtener URL pública
    const { data } = supabase.storage
    .from('avatars')
    .getPublicUrl('user-id/avatar.jpg');

    Los archivos privados usan createSignedUrl para generar enlaces temporales

FAQ

¿Cuáles son las diferencias principales entre Supabase y Firebase?
Supabase usa PostgreSQL, una base de datos relacional con consultas SQL complejas y relaciones claras; Firebase usa Firestore, un NoSQL documental con consultas más limitadas. Supabase es totalmente open source, con precio fijo de 25 $/mes y control total de los datos; Firebase es cerrado, factura por uso (puede superar 800 $/mes) y exportar datos es engorroso.
¿Para qué tipo de proyectos encaja Supabase?
Encaja bien en estos escenarios:

• Relaciones de datos complejas que requieren SQL y joins
• Proyectos a largo plazo con datos bajo tu control
• Presupuesto ajustado (coste fijo más predecible)
• Desarrolladores frontend que quieren apps full stack rápido
• Prioridad open source y stack tecnológico controlable
¿Qué es Row Level Security (RLS) y para qué sirve?
RLS es la seguridad a nivel de fila de PostgreSQL: controla en la base de datos qué filas puede ver o modificar cada usuario. Con auth.uid() de Supabase Auth, un usuario autenticado solo accede a sus propios registros, más seguro que validar solo en el frontend.
¿Qué métodos de inicio de sesión soporta Supabase Auth?
Soporta varios métodos:

• Email/Password (correo y contraseña)
• Magic Link (sin contraseña)
• OTP (contraseña de un solo uso)
• Social Auth (Google, GitHub, Apple y más de 20 plataformas)
• Phone Auth (Twilio, MessageBird)
• SSO (inicio de sesión único empresarial)
¿Qué diferencia hay entre buckets públicos y privados en Supabase Storage?
En un bucket público (Public Bucket) cualquiera puede acceder a los archivos, ideal para imágenes o avatares públicos; en uno privado (Private Bucket) hace falta autorización, usando createSignedUrl para enlaces temporales con caducidad configurable, ideal para documentos privados o archivos subidos por usuarios.
¿Cuánto tarda migrar de Firebase a Supabase?
Una app sencilla puede migrarse en 2-3 días: convertir datos de Firestore a tablas PostgreSQL, exportar usuarios de Firebase Auth a Supabase Auth y mover archivos de Cloud Storage a Supabase Storage. Apps complejas pueden llevar 1-2 semanas, sobre todo por el modelo de datos y reescribir consultas.
¿Qué incluye el plan gratuito de Supabase?
El plan gratuito incluye: solicitudes API ilimitadas, 50.000 usuarios activos al mes, 500 MB de base de datos, 1 GB de almacenamiento de archivos y 5 GB de ancho de banda. Sirve para MVPs personales; al superar límites hay que pasar al Pro Plan (25 $/mes).

15 min de lectura · Publicado el: 3 abr 2026 · Actualizado el: 21 ago 2026

Comentarios

Inicia sesión con GitHub para dejar un comentario

Easton BlogEaston Blog