Diciembre en Cuenca significa lluvia, y lluvia significa quedarme en casa todo el fin de semana. Liss fue a pasar el fin de semana con su familia, así que aproveché para hacer lo que todo developer casado hace cuando está solo: empezar un proyecto que probablemente nunca termine.
Esta vez decidí probar Supabase, ese "Firebase open source" del que todos hablan. Spoiler: es mejor de lo que esperaba, y eso que mis expectativas eran más bajas que mi motivación un lunes.
¿Qué diablos es Supabase?
Imagina PostgreSQL con esteroides, authentication que funciona, storage que no te cobra un riñón, y realtime que actually es real time. Todo eso con una UI que no parece diseñada en 1995.
Básicamente es lo que Firebase debió ser si Google no fuera... bueno, Google.
Setup inicial: Más fácil que mentirle a tu PM sobre el progreso
Primero, crear un proyecto en supabase.com
. Es gratis hasta cierto punto, como mi paciencia con WordPress.
# Instalar Supabase en tu proyecto
npm install @supabase/supabase-js
# O si eres team yarn como yo
yarn add @supabase/supabase-js
Crear el cliente es más simple que explicarle a Juanito por qué no puede usar mi tarjeta de crédito para Fortnite:
// supabase.js
import { createClient } from '@supabase/supabase-js'
const supabaseUrl = 'https://tuproyecto.supabase.co'
const supabaseKey = 'tu-anon-key-que-no-es-secreta'
export const supabase = createClient(supabaseUrl, supabaseKey)
// Sí, la anon key va en el frontend. No, no es inseguro.
// Sí, me tomó 2 horas entender por qué.
Authentication: Login que funciona sin vender tu alma
Implementar auth con Supabase es tan fácil que me sentí estafado. Años configurando JWT, refresh tokens, y estas madres... para nada.
// Registro de usuario
const { data, error } = await supabase.auth.signUp({
email: 'developer@cansado.com',
password: 'password123', // No uses esto en producción, animal
})
// Login
const { data, error } = await supabase.auth.signInWithPassword({
email: 'developer@cansado.com',
password: 'password123'
})
// Login con Google (porque nadie quiere recordar otra password)
const { data, error } = await supabase.auth.signInWithOAuth({
provider: 'google',
})
// Logout (cuando tu jefe se acerca)
const { error } = await supabase.auth.signOut()
Lo mejor: maneja sesiones automáticamente. No más tokens en localStorage como en 2015.
Base de datos: PostgreSQL sin el trauma
Crear tablas desde la UI es tan intuitivo que me dio vergüenza. Pero si eres masoquista como yo, puedes usar SQL directamente:
-- Crear una tabla para proyectos abandonados
CREATE TABLE proyectos_abandonados (
id BIGSERIAL PRIMARY KEY,
nombre TEXT NOT NULL,
razon_abandono TEXT,
nivel_entusiasmo_inicial INTEGER DEFAULT 10,
nivel_entusiasmo_final INTEGER DEFAULT 0,
created_at TIMESTAMP WITH TIME ZONE DEFAULT TIMEZONE('utc', NOW())
);
-- RLS (Row Level Security) - Porque la seguridad importa
ALTER TABLE proyectos_abandonados ENABLE ROW LEVEL SECURITY;
-- Solo el usuario puede ver sus propios proyectos de vergüenza
CREATE POLICY "Usuarios ven sus propios proyectos" ON proyectos_abandonados
FOR ALL USING (auth.uid() = user_id);
El verdadero poder está en el cliente JavaScript:
// Insertar datos (mi historia de vida)
const { data, error } = await supabase
.from('proyectos_abandonados')
.insert([
{
nombre: 'App de finanzas personales #47',
razon_abandono: 'Descubrí que necesitaba saber finanzas',
nivel_entusiasmo_inicial: 10,
nivel_entusiasmo_final: 0
}
])
// Leer datos
const { data, error } = await supabase
.from('proyectos_abandonados')
.select('*')
.order('created_at', { ascending: false })
// Actualizar cuando recuperas la fe
const { data, error } = await supabase
.from('proyectos_abandonados')
.update({ nivel_entusiasmo_final: 2 })
.eq('id', 1)
// Borrar evidencia
const { data, error } = await supabase
.from('proyectos_abandonados')
.delete()
.eq('id', 1)
Realtime: Porque refrescar la página es de 2010
Esta parte me voló la cabeza. Realtime que funciona sin configurar WebSockets ni sacrificar una cabra:
// Suscribirse a cambios
const subscription = supabase
.channel('proyectos-changes')
.on(
'postgres_changes',
{
event: '*', // INSERT, UPDATE, DELETE
schema: 'public',
table: 'proyectos_abandonados'
},
(payload) => {
console.log('Alguien más abandonó un proyecto:', payload)
// Actualizar UI para no sentirnos solos
}
)
.subscribe()
// Limpieza (importante, como bañarse)
subscription.unsubscribe()
Storage: Para guardar assets sin quebrar
Subir archivos es tan simple que duele:
// Crear bucket (una sola vez)
const { data, error } = await supabase
.storage
.createBucket('screenshots-de-bugs', {
public: true // o false si es contenido vergonzoso
})
// Subir archivo
const { data, error } = await supabase
.storage
.from('screenshots-de-bugs')
.upload('bug-que-no-es-feature.png', file, {
cacheControl: '3600',
upsert: false
})
// Obtener URL pública
const { data } = supabase
.storage
.from('screenshots-de-bugs')
.getPublicUrl('bug-que-no-es-feature.png')
Edge Functions: Serverless sin AWS
Porque a veces necesitas lógica en el servidor (como validar que el usuario no está mintiendo sobre su experiencia):
// functions/validate-experience/index.ts
import { serve } from 'https://deno.land/std@0.168.0/http/server.ts'
serve(async (req) => {
const { years, framework } = await req.json()
// Validación real de CVs
if (framework === 'React' && years > 10) {
return new Response(
JSON.stringify({ valid: false, reason: 'React salió en 2013, no mientas' }),
{ headers: { "Content-Type": "application/json" } }
)
}
return new Response(
JSON.stringify({ valid: true }),
{ headers: { "Content-Type": "application/json" } }
)
})
El proyecto completo: Todo Manager 3000
Para no ser puro bla bla, armé un mini todo app (original, lo sé):
// App.jsx - El componente principal
import { useState, useEffect } from 'react'
import { supabase } from './supabase'
function App() {
const [todos, setTodos] = useState([])
const [loading, setLoading] = useState(true)
const [user, setUser] = useState(null)
useEffect(() => {
// Check user
supabase.auth.getSession().then(({ data: { session } }) => {
setUser(session?.user ?? null)
})
// Listen auth changes
const { data: { subscription } } = supabase.auth.onAuthStateChange((_event, session) => {
setUser(session?.user ?? null)
})
return () => subscription.unsubscribe()
}, [])
useEffect(() => {
if (user) {
fetchTodos()
// Realtime subscription
const subscription = supabase
.channel('todos-channel')
.on('postgres_changes',
{ event: '*', schema: 'public', table: 'todos' },
handleRealtimeUpdate
)
.subscribe()
return () => subscription.unsubscribe()
}
}, [user])
const fetchTodos = async () => {
const { data, error } = await supabase
.from('todos')
.select('*')
.order('created_at', { ascending: false })
if (data) setTodos(data)
setLoading(false)
}
const handleRealtimeUpdate = (payload) => {
if (payload.event === 'INSERT') {
setTodos(prev => [payload.new, ...prev])
} else if (payload.event === 'DELETE') {
setTodos(prev => prev.filter(todo => todo.id !== payload.old.id))
}
// UPDATE lo dejo de tarea
}
if (!user) {
return // Implementar esto es tu tarea
}
return (
{/* UI aquí - también tu tarea */}
)
}
Cosas que aprendí a las malas
1. RLS es tu amigo paranoico
Row Level Security es obligatorio. Sin él, cualquiera puede ver/editar/borrar todo. Aprendí esto cuando mi tabla de prueba desapareció misteriosamente.
2. Las keys no son tan públicas
La anon key
va en el frontend, sí. Pero la service key
es sagrada. Más sagrada que el último Red Bull del refri.
3. El free tier es generoso... hasta que no lo es
500MB de storage y 2GB de bandwidth al mes. Suena mucho hasta que subes videos de bugs en 4K.
4. SQL knowledge sigue siendo necesario
Puedes hacer mucho con la UI, pero eventualmente necesitarás SQL. Es como CSS - todos dicen que no lo necesitas hasta que lo necesitas.
¿Vale la pena?
Después de un fin de semana completo jugando con Supabase (y 6 Red Bulls), mi veredicto es: SÍ.
Es perfecto para:
- MVPs que necesitas sacar ya
- Side projects que probablemente abandones
- Apps reales si no necesitas cosas muy locas
- Cuando Firebase te tiene hasta la madre
NO es para:
- Si necesitas hosting (solo es backend)
- Aplicaciones con lógica de negocio muy compleja
- Si tu jefe tiene miedo al open source
¿Ya probaron Supabase? ¿O siguen configurando backends desde cero como psicópatas? Me encantaría saber sus experiencias, especialmente si lograron convencer a sus empresas de usarlo.
PD: Si van a seguir este tutorial, háganlo con música. Configurar backends en silencio es deprimente. Yo lo hice escuchando synthwave y fingiendo que estoy en una película de hackers.
PD2: Liss volvió de Quito y vio mi "app de todos" funcionando. Su comentario: "¿Otra lista de tareas? ¿En serio?". El matrimonio es hermoso.
PD3: Si alguien de Supabase lee esto: agreguen un modo oscuro que no queme retinas a las 3 AM. Mi oftalmólogo y yo se los agradeceremos.
Comentarios 1
Comparte tu opinión
Rafael Torres