🎯 Objetivo del tema
El administrador aún no ha insertado un video para esta sección.
🗺️ Mapa del tema
En los temas anteriores construiste tu backend desde cero: Express, JWT, PostgreSQL, MongoDB, deploy. Funciona, pero toma tiempo. Hoy aprenderás BaaS (Backend as a Service): Supabase te da auth, BD PostgreSQL, storage, realtime, y edge functions como servicios listos para usar. Tú solo defines la lógica de tu app. Es la elección de muchas startups en 2024-2025 para acelerar el time-to-market.
El 'Firebase de open source': backend sin escribir toda la infraestructura.
Email/password, OAuth, magic links, y la SDK @supabase/supabase-js.
Políticas de seguridad a nivel de fila: el frontend nunca accede a datos que no debe.
Imágenes, PDFs, videos: el equivalente Supabase de AWS S3.
WebSockets para que tu UI se actualice sin recargar.
Deno functions que se ejecutan en el edge, cerca del usuario.
2.12.1 Qué es BaaS y por qué Supabase
BaaS (Backend as a Service) es un modelo donde un proveedor te da el backend listo para usar: autenticación, BD, storage, realtime, funciones. Tú solo defines las reglas de tu app. Supabase es la alternativa open source a Firebase (Google): usa PostgreSQL real (no NoSQL) y es 100% open source. En 2024-2025, es la opción más popular para startups y proyectos rápidos.
| Característica | Firebase (Google) | Supabase (open source) |
|---|---|---|
| Base de datos | Firestore (NoSQL, propietario). | PostgreSQL (SQL, open source). |
| Auth | Firebase Auth. | Supabase Auth (basado en GoTrue). |
| Storage | Cloud Storage. | Supabase Storage (S3-compatible). |
| Realtime | Firestore listeners. | PostgreSQL LISTEN/NOTIFY + WebSockets. |
| Functions | Cloud Functions. | Edge Functions (Deno). |
| Lock-in | Total: si te vas, rehaces todo. | Bajo: PostgreSQL estándar, exportable. |
| Open source | No. | Sí, 100%. |
| Self-hosting | No. | Sí, puedes hostearlo tú mismo. |
El administrador aún no ha insertado un video para esta sección.
2.12.2 Auth: autenticación lista para usar
Supabase Auth te da autenticación completa sin escribir código de backend: email/password, magic links (login por email sin contraseña), OAuth (Google, GitHub, etc.), y manejo de sesiones. Solo configuras en el dashboard y usas la SDK en tu frontend. Es la forma más rápida de tener auth production-ready.
// Instalación: npm install @supabase/supabase-js
import { createClient } from '@supabase/supabase-js';
// Conectar a tu proyecto de Supabase (en .env, NO subir a Git)
const supabase = createClient(
process.env.SUPABASE_URL,
process.env.SUPABASE_ANON_KEY // 'anon' = puede ser público, pero con RLS está protegido
);
// REGISTRO: email + password
export async function registrar(email, password) {
const { data, error } = await supabase.auth.signUp({ email, password });
if (error) throw error;
return data.user;
}
// LOGIN: email + password
export async function login(email, password) {
const { data, error } = await supabase.auth.signInWithPassword({ email, password });
if (error) throw error;
return { user: data.user, session: data.session };
}
// LOGIN con magic link (sin contraseña, solo email)
export async function loginConMagicLink(email) {
const { data, error } = await supabase.auth.signInWithOtp({ email });
if (error) throw error;
return data; // el usuario recibe un link por email
}
// LOGIN con Google OAuth
export async function loginConGoogle() {
const { data, error } = await supabase.auth.signInWithOAuth({ provider: 'google' });
if (error) throw error;
return data;
}
// LOGOUT
export async function logout() {
const { error } = await supabase.auth.signOut();
if (error) throw error;
}
// OBTENER el usuario actual
export async function getUser() {
const { data: { user } } = await supabase.auth.getUser();
return user;
}Auth de Supabase: registro, login, magic link, OAuth
El administrador aún no ha insertado un video para esta sección.
2.12.3 PostgreSQL con Row Level Security (RLS)
RLS (Row Level Security) es la característica de Supabase/PostgreSQL que hace que cada usuario SOLO pueda ver/modificar SUS PROPIOS datos, sin que tengas que escribir la lógica en tu backend. Se configura con políticas SQL a nivel de fila. Es la magia que hace a Supabase seguro para apps multi-tenant.
Ejemplo: tabla 'posts' con RLS
-- 1. Crear la tabla (normal)
CREATE TABLE posts (
id UUID PRIMARY KEY DEFAULT gen_random_uuid(),
user_id UUID REFERENCES auth.users(id) NOT NULL,
titulo TEXT NOT NULL,
contenido TEXT NOT NULL,
creado_en TIMESTAMPTZ DEFAULT NOW()
);
-- 2. ACTIVAR RLS (CRÍTICO: sin esto, todas las políticas son ignoradas)
ALTER TABLE posts ENABLE ROW LEVEL SECURITY;
-- 3. Política: cada user solo ve SUS posts
CREATE POLICY "Users can view their own posts"
ON posts FOR SELECT
USING (auth.uid() = user_id);
-- 4. Política: cada user solo puede insertar posts con SU user_id
CREATE POLICY "Users can insert their own posts"
ON posts FOR INSERT
WITH CHECK (auth.uid() = user_id);
-- 5. Política: cada user solo puede actualizar SUS posts
CREATE POLICY "Users can update their own posts"
ON posts FOR UPDATE
USING (auth.uid() = user_id);
-- 6. Política: cada user solo puede eliminar SUS posts
CREATE POLICY "Users can delete their own posts"
ON posts FOR DELETE
USING (auth.uid() = user_id);
-- Ahora: si user A intenta ver los posts de user B, la query devuelve [].
-- Si user A intenta crear un post con user_id = user B, falla.
-- La seguridad está en la BD, no en tu código. No hay manera de saltársela desde el frontend.Row Level Security: políticas por usuario
El administrador aún no ha insertado un video para esta sección.
2.12.4 Storage: subir y servir archivos
Supabase Storage te permite subir y servir archivos: imágenes de perfil, fotos de productos, PDFs, videos. Es el equivalente open source de AWS S3, con la misma interfaz simple. Tiene políticas de acceso (públicas, privadas, o por usuario) e integración nativa con tu auth.
// Subir una imagen de perfil del usuario actual
export async function subirAvatar(file) {
const user = await getUser();
const fileExt = file.name.split('.').pop();
const fileName = `${user.id}-${Date.now()}.${fileExt}`;
const filePath = `avatars/${fileName}`;
const { data, error } = await supabase.storage
.from('avatars') // nombre del bucket
.upload(filePath, file, { cacheControl: '3600', upsert: true });
if (error) throw error;
// Obtener la URL pública (si el bucket es público)
const { data: { publicUrl } } = supabase.storage
.from('avatars')
.getPublicUrl(filePath);
return publicUrl;
}
// Subir un archivo privado (solo el dueño puede acceder)
export async function subirArchivoPrivado(file) {
const user = await getUser();
const filePath = `${user.id}/${file.name}`;
const { data, error } = await supabase.storage
.from('archivos-privados')
.upload(filePath, file);
if (error) throw error;
return filePath;
}
// Generar una URL temporal (válida por 1 hora)
export async function getUrlPrivada(filePath) {
const { data: { signedUrl }, error } = await supabase.storage
.from('archivos-privados')
.createSignedUrl(filePath, 3600); // 1 hora
if (error) throw error;
return signedUrl;
}
// Eliminar archivo
export async function eliminarArchivo(bucket, filePath) {
const { error } = await supabase.storage
.from(bucket)
.remove([filePath]);
if (error) throw error;
}Storage: subir, obtener URL pública, URL temporal
El administrador aún no ha insertado un video para esta sección.
2.12.5 Realtime: suscripciones en vivo
Supabase Realtime te permite suscribirte a cambios en tu BD: cuando alguien inserta un post, tu UI se actualiza automáticamente. Sin recargar, sin polling, sin WebSockets manuales. Es ideal para chats, dashboards en vivo, feeds de actividad, y cualquier UI que necesite mostrar datos actualizados en tiempo real.
// Suscribirse a INSERTs en la tabla 'posts'
const canal = supabase
.channel('posts-feed')
.on(
'postgres_changes',
{ event: 'INSERT', schema: 'public', table: 'posts' },
(payload) => {
console.log('Nuevo post:', payload.new);
// Actualizar la UI: prepend the new post to the list
setPosts(prev => [payload.new, ...prev]);
}
)
.subscribe();
// Suscribirse a UPDATES (para editar posts en vivo)
const canalUpdate = supabase
.channel('posts-update')
.on(
'postgres_changes',
{ event: 'UPDATE', schema: 'public', table: 'posts' },
(payload) => {
console.log('Post actualizado:', payload.new);
setPosts(prev => prev.map(p => p.id === payload.new.id ? payload.new : p));
}
)
.subscribe();
// Limpiar la suscripción al desmontar el componente
useEffect(() => {
return () => {
supabase.removeChannel(canal);
supabase.removeChannel(canalUpdate);
};
}, []);
// También puedes usar Realtime en el BACKEND (Node.js) para, por ejemplo,
// notificar al cliente cuando termine un job largo:
const canalServer = supabase.channel('job-status');
await canalServer.send({
type: 'broadcast',
event: 'job-completed',
payload: { jobId: '123', result: 'success' },
});Realtime: suscripciones a cambios en la BD
El administrador aún no ha insertado un video para esta sección.
2.12.6 Edge Functions: lógica custom en el serverless
Edge Functions son funciones serverless que se ejecutan en el 'edge' (cerca del usuario), escritas en TypeScript con Deno. Son perfectas para: webhooks (Stripe, GitHub), tareas programadas (cron), lógica de backend que necesita ejecutarse en el server, o integraciones con APIs externas. Se despliegan con un comando: supabase functions deploy nombre-funcion.
// supabase/functions/stripe-webhook/index.ts
// Edge Function que recibe webhooks de Stripe
import { serve } from 'https://deno.land/std@0.168.0/http/server.ts';
import { createClient } from 'https://esm.sh/@supabase/supabase-js@2';
import Stripe from 'https://esm.sh/stripe@13.0.0';
const stripe = new Stripe(Deno.env.get('STRIPE_SECRET_KEY')!, {
apiVersion: '2023-10-16',
});
const supabase = createClient(
Deno.env.get('SUPABASE_URL')!,
Deno.env.get('SUPABASE_SERVICE_ROLE_KEY')! // service_role bypasea RLS
);
serve(async (req) => {
const signature = req.headers.get('stripe-signature');
const body = await req.text();
try {
// Verificar la firma del webhook (CRÍTICO para seguridad)
const event = await stripe.webhooks.constructEventAsync(
body, signature!, Deno.env.get('STRIPE_WEBHOOK_SECRET')!
);
if (event.type === 'checkout.session.completed') {
const session = event.data.object;
const userId = session.metadata.userId;
const plan = session.metadata.plan;
// Actualizar el plan del usuario en Supabase
await supabase.from('usuarios')
.update({ plan, suscripcion_activa: true })
.eq('id', userId);
}
return new Response(JSON.stringify({ received: true }), { status: 200 });
} catch (err) {
return new Response(`Webhook error: ${err.message}`, { status: 400 });
}
});
// Para desplegar:
// supabase functions deploy stripe-webhook --no-verify-jwtEdge Function: webhook de Stripe en Supabase
El administrador aún no ha insertado un video para esta sección.
📚 Contenido ampliado
Material adicional, referencias externas verificadas y ejemplos extendidos.
🤖 AI Mission
Supabase se aprende construyendo algo end-to-end, no leyendo documentación.
Misión: Construye una app completa de 'posts' (tipo Twitter) con Supabase: auth (email/password), tabla 'posts' con RLS, Storage para avatares, y Realtime para que el feed se actualice en vivo. Deploy en Vercel o Netlify. Pídele a la IA que audite tu RLS. Anota en tu cuaderno: ¿qué es lo más poderoso de Supabase? ¿qué limitaciones encontraste?
Pasos sugeridos
- Crea proyecto en supabase.com. Anota URL y ANON_KEY en .env.
- Crea la tabla 'posts' con SQL del ejemplo (id, user_id, titulo, contenido, creado_en).
- Activa RLS: ALTER TABLE posts ENABLE ROW LEVEL SECURITY.
- Crea las 4 políticas: SELECT, INSERT, UPDATE, DELETE, cada una con USING (auth.uid() = user_id).
- Crea un bucket 'avatars' en Storage (público para imágenes de perfil).
- Crea el frontend (puede ser HTML+JS simple o React). Conecta con @supabase/supabase-js.
- Implementa: registro, login, crear post (con avatar opcional), listar posts en tiempo real con Realtime.
- Prueba con 2 usuarios: user A crea un post, user B lo ve aparecer en su feed SIN recargar.
- Pídele a la IA: 'Tengo esta app de posts en Supabase. Audita la seguridad: RLS, anon_key, policies. Dame 2 mejoras con código SQL.'
- Aplica 2 mejoras. Vuelve a probar.
- Anota: 3 cosas que Supabase te ahorró que habrías escrito a mano, y 1 limitación que encontraste.
📓 Entregable: URL de tu app de posts en producción, capturas de: RLS funcionando (user A no ve posts de user B), realtime (feed se actualiza en vivo), código SQL de las políticas, y media página de cuaderno con tu reflexión.
🚫 Errores típicos de razonamiento
Por qué: La ANON_KEY está diseñada para ser pública (la pones en tu frontend). Sin RLS, cualquiera con esa key puede leer/escribir TODAS las filas de tus tablas. Es un agujero de seguridad GRAVÍSIMO. SIEMPRE ejecuta ALTER TABLE ... ENABLE ROW LEVEL SECURITY y crea políticas para cada operación (SELECT, INSERT, UPDATE, DELETE). Es la diferencia entre una app segura y un desastre de seguridad.
Por qué: La SERVICE_ROLE_KEY bypasea TODAS las políticas de RLS: es acceso de admin total. Si la pones en el frontend, cualquiera puede hacer lo que quiera con tu BD. Solo úsala en código del servidor (Edge Functions, scripts internos). Si se filtra, ROTA inmediatamente desde el dashboard de Supabase. Es la clave más sensible de tu app.
Por qué: RLS protege el acceso a filas, pero NO la integridad de los datos. Si un usuario envía un post con titulo = '<script>alert("xss")</script>', RLS lo permite, pero cuando se muestre en otros usuarios, se ejecuta el script. SIEMPRE valida y sanitiza el input en el cliente (y en RLS policies con CHECK constraints) antes de enviar a la BD. Es defensa en profundidad.
Por qué: Cada suscripción Realtime abre una conexión WebSocket. Si tu componente se desmonta (el usuario navega a otra página) y no cierras la suscripción, la conexión se queda abierta PARA SIEMPRE. Después de navegar 20 veces, tienes 20 conexiones abiertas. SIEMPRE limpia en useEffect: return () => supabase.removeChannel(canal).
Por qué: Si subes tu código a Git con las keys hardcodeadas, cualquiera puede acceder a tu proyecto. SIEMPRE usa variables de entorno (.env) y agrégalo a .gitignore. En producción, configúralas en tu plataforma de deploy (Vercel, Netlify, Railway).
🧪 Laboratorio práctico
Supabase es la navaja suiza del backend moderno: úsalo con respeto.
Laboratorio: App completa de posts con Supabase (auth + RLS + Storage + Realtime)
Objetivo: Construir una mini red social de posts con Supabase: autenticación con email/password, tabla 'posts' con RLS, Storage para avatares, Realtime para feed en vivo, y deploy en Vercel. El resultado: una app full-stack que tarda 2 horas en construir y es production-ready.
Pasos
- Crea proyecto en supabase.com. Anota URL y ANON_KEY en .env.
- Crea la tabla 'posts' y 'profiles' con SQL.
- Activa RLS en ambas tablas. Crea políticas: cada user solo ve/edita SUS datos.
- Crea bucket 'avatars' (público).
- Crea el frontend (HTML+JS o React). Conecta con @supabase/supabase-js desde CDN.
- Implementa: registro, login, crear post, listar posts, subir avatar.
- Implementa Realtime: el feed se actualiza en vivo cuando alguien crea un post.
- Prueba con 2 navegadores: user A crea post, user B lo ve aparecer SIN recargar.
- Verifica RLS: en la consola del navegador, intenta hacer queries con el anon_key directo. Solo debe devolver SUS posts.
- Deploy en Vercel/Netlify. Configura las env vars.
- Comparte la URL pública. Que 2 personas se registren y prueben el realtime.
- Haz commit: 'feat: app de posts con Supabase (auth + RLS + Storage + Realtime)'.
📓 Entregable: URL pública de la app, capturas de: registro, login, creación de post, realtime funcionando con 2 usuarios, código SQL de las políticas, y commit en Git.