Saltar al contenido principal

Supabase

Backend as a Service basado en PostgreSQL. Incluye base de datos, autenticación, almacenamiento de archivos, funciones edge y suscripciones en tiempo real — todo gratis en el plan Free.

Límites del plan gratuito

ParámetroValor
MotorPostgreSQL
Storage DB500 MB
Storage archivos1 GB
Proyectos activos2 proyectos
Pausa por inactividad⚠️ 1 semana sin actividad
Auth (MAU)50,000 usuarios activos/mes
Realtime✅ Incluido
Edge Functions500,000 invocaciones/mes
Bandwidth5 GB/mes
Backups❌ No en free
OrganizacionesIlimitadas

Crear proyecto

1. https://supabase.com → New Project
2. Organización: crear o seleccionar existente
3. Configurar:
- Name: mi-proyecto
- Database Password: elegir una contraseña segura (¡guardarla!)
- Region: elegir la más cercana (South America São Paulo, US East, EU Frankfurt)
4. Esperar ~2 minutos a que se aprovisione

Obtener credenciales

Settings → API
├── Project URL: https://xxxx.supabase.co
├── anon (public) key: eyJhb... ← para el cliente/frontend
└── service_role key: eyJhb... ← SOLO en el servidor, nunca en el cliente

Instalación del cliente

npm install @supabase/supabase-js

Configurar cliente

// lib/supabase.ts
import { createClient } from '@supabase/supabase-js'

const supabaseUrl = process.env.NEXT_PUBLIC_SUPABASE_URL!
const supabaseKey = process.env.NEXT_PUBLIC_SUPABASE_ANON_KEY!

export const supabase = createClient(supabaseUrl, supabaseKey)
# .env.local
NEXT_PUBLIC_SUPABASE_URL=https://xxxx.supabase.co
NEXT_PUBLIC_SUPABASE_ANON_KEY=eyJhb...
SUPABASE_SERVICE_ROLE_KEY=eyJhb... # solo servidor

Crear tablas

Opción 1: Table Editor (GUI)

Table Editor → New Table
→ Name: usuarios
→ Columns: id (uuid, primary key), nombre (text), email (text, unique), created_at (timestamp)
→ Enable RLS: ✅ (recomendado)

Opción 2: SQL Editor

-- SQL Editor → New query
CREATE TABLE usuarios (
id UUID DEFAULT gen_random_uuid() PRIMARY KEY,
nombre TEXT NOT NULL,
email TEXT UNIQUE NOT NULL,
activo BOOLEAN DEFAULT true,
created_at TIMESTAMPTZ DEFAULT NOW()
);

-- Habilitar Row Level Security
ALTER TABLE usuarios ENABLE ROW LEVEL SECURITY;

Operaciones CRUD

// SELECT
const { data, error } = await supabase
.from('usuarios')
.select('*')
.eq('activo', true)
.order('created_at', { ascending: false })
.limit(10)

// SELECT con relaciones
const { data } = await supabase
.from('posts')
.select(`
id, titulo, contenido,
autor:usuarios(nombre, email)
`)

// INSERT
const { data, error } = await supabase
.from('usuarios')
.insert({ nombre: 'Juan', email: 'juan@email.com' })
.select() // retorna el registro creado

// UPDATE
const { data, error } = await supabase
.from('usuarios')
.update({ activo: false })
.eq('id', userId)
.select()

// DELETE
const { error } = await supabase
.from('usuarios')
.delete()
.eq('id', userId)

// UPSERT (insert o update si existe)
const { data, error } = await supabase
.from('configuracion')
.upsert({ user_id: userId, tema: 'dark' })
.select()

Autenticación

// Registrar usuario
const { data, error } = await supabase.auth.signUp({
email: 'usuario@email.com',
password: 'contraseña123',
options: {
data: { nombre: 'Juan' } // metadata adicional
}
})

// Login con email/password
const { data, error } = await supabase.auth.signInWithPassword({
email: 'usuario@email.com',
password: 'contraseña123',
})

// Login con OAuth (Google, GitHub, etc.)
await supabase.auth.signInWithOAuth({
provider: 'google',
options: {
redirectTo: 'https://mi-app.com/auth/callback'
}
})

// Logout
await supabase.auth.signOut()

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

// Escuchar cambios de sesión
supabase.auth.onAuthStateChange((event, session) => {
if (event === 'SIGNED_IN') console.log('Logueado:', session?.user)
if (event === 'SIGNED_OUT') console.log('Deslogueado')
})

Row Level Security (RLS)

RLS controla qué datos puede ver/modificar cada usuario. Siempre activarlo en producción.

-- Política: cada usuario solo ve sus propios datos
CREATE POLICY "usuarios_propios" ON usuarios
FOR ALL
USING (auth.uid() = user_id);

-- Política: lectura pública, escritura solo autenticados
CREATE POLICY "lectura_publica" ON posts
FOR SELECT USING (true);

CREATE POLICY "escritura_autenticada" ON posts
FOR INSERT WITH CHECK (auth.uid() IS NOT NULL);

Realtime (suscripciones)

// Escuchar cambios en una tabla
const channel = supabase
.channel('cambios-mensajes')
.on(
'postgres_changes',
{ event: '*', schema: 'public', table: 'mensajes' },
(payload) => {
console.log('Cambio recibido:', payload)
if (payload.eventType === 'INSERT') {
// nuevo mensaje...
}
}
)
.subscribe()

// Desuscribirse
supabase.removeChannel(channel)

Storage (archivos)

// Subir archivo
const { data, error } = await supabase.storage
.from('avatares') // nombre del bucket
.upload(`${userId}/avatar.png`, file, {
contentType: 'image/png',
upsert: true
})

// Obtener URL pública
const { data } = supabase.storage
.from('avatares')
.getPublicUrl(`${userId}/avatar.png`)

console.log(data.publicUrl)

// Eliminar archivo
await supabase.storage
.from('avatares')
.remove([`${userId}/avatar.png`])

Edge Functions

# Instalar Supabase CLI
npm install -g supabase

# Login
supabase login

# Crear función
supabase functions new mi-funcion

# Estructura creada: supabase/functions/mi-funcion/index.ts
// supabase/functions/mi-funcion/index.ts
import { serve } from "https://deno.land/std@0.177.0/http/server.ts"
import { createClient } from "https://esm.sh/@supabase/supabase-js@2"

serve(async (req) => {
const supabase = createClient(
Deno.env.get('SUPABASE_URL')!,
Deno.env.get('SUPABASE_SERVICE_ROLE_KEY')!
)

const { data } = await supabase.from('usuarios').select('*')
return new Response(JSON.stringify(data), {
headers: { 'Content-Type': 'application/json' }
})
})
# Desplegar función
supabase functions deploy mi-funcion --project-ref tu-project-ref

Prevenir la pausa automática

Supabase pausa proyectos inactivos después de 1 semana en el plan free.

Solución con cron-job.org (gratuito):

  1. Registrarse en https://cron-job.org
  2. Create cronjob:
    • URL: https://tu-proyecto.supabase.co/rest/v1/cualquier-tabla?select=id&limit=1
    • Headers: apikey: tu-anon-key
    • Interval: cada 3 días
  3. Esto mantiene el proyecto activo sin costo

Organizaciones y proyectos

Organización
└── Proyecto 1 (500 MB DB, 1 GB Storage)
└── Proyecto 2 (500 MB DB, 1 GB Storage)
  • Máximo 2 proyectos activos por organización en free
  • Para más proyectos: pausar uno antes de crear otro
  • Las organizaciones son gratuitas e ilimitadas

⚠️ Limitaciones importantes

  • Pausa por inactividad: el mayor problema del free tier
  • Sin backups automáticos: hacer exports manuales regularmente desde SQL Editor → Export
  • 5 GB bandwidth: puede agotarse con apps de muchas imágenes
  • 500 MB DB: suficiente para la mayoría de proyectos de dev/prueba