SaaS Starter

Supabase

Desplegá este boilerplate contra Supabase Postgres + Supabase Storage con el menor diff de config posible.

Supabase es un target de deploy first-class para este boilerplate. Obtenés Postgres + un bucket de storage S3-compatible de un único vendor, y las abstracciones existentes del boilerplate de Prisma + storage se enchufan sin ningún cambio de código — sólo env vars.

Esta página cubre el path recomendado: Supabase como backend de database y de file-storage, con BetterAuth corriendo encima de Prisma contra la instancia de Postgres de Supabase. No usamos Supabase Auth — BetterAuth ya es dueño de los flows de auth, sessions, y wiring de OAuth en este codebase.

Fuera de scope: Supabase Realtime y Edge Functions. El boilerplate trae su propio stack Socket.IO + BullMQ para esos concerns.

1. Crear un proyecto Supabase

  1. Iniciá sesión en supabase.com y creá un proyecto nuevo.
  2. Elegí una contraseña fuerte para la database (la vas a necesitar en el paso 2).
  3. Esperá a que el proyecto se provisione (~2 minutos).

2. Tomar las connection strings

Abrí Project Settings → Database. Necesitás dos connection strings:

VariableSource (Supabase UI)Por qué
DATABASE_URL"Connection pooling" → Transaction modePooled (port 6543); usado por la app corriendo para queries cortos
DIRECT_URL"Connection string" → URIDirect (port 5432); usado por prisma migrate para advisory locks

Agregá ?pgbouncer=true&connect_timeout=15 a DATABASE_URL para que Prisma sepa que está hablando con un pooler.

# .env (server)
DATABASE_URL="postgresql://postgres.<ref>:<password>@aws-0-<region>.pooler.supabase.com:6543/postgres?pgbouncer=true&connect_timeout=15"
DIRECT_URL="postgresql://postgres.<ref>:<password>@aws-0-<region>.pooler.supabase.com:5432/postgres"

¿Por qué dos URLs? El pooler pgbouncer de Supabase desnuda los advisory locks y las sessions long-lived, en los que las migrations de Prisma se apoyan. El runtime usa la URL pooled por eficiencia de conexión; las migrations usan la URL directa. El prisma/schema.prisma block datasource db ya declara directUrl = env("DIRECT_URL") por esta razón.

3. Correr migrations

Desde la raíz del repo:

bunx prisma migrate deploy --schema apps/server/prisma/schema.prisma

Prisma va a usar DIRECT_URL automáticamente. Verificá que las tablas aterrizaron vía Table Editor en la UI de Supabase.

4. (Opcional) Cambiar storage a Supabase

Si querés que los uploads de avatares y todo upload futuro vayan a Supabase Storage en lugar de S3 / Cloudflare R2 / disco local:

  1. Crear un bucket. En Storage → New bucket, nombralo (ej. avatars). Marcalo "Public" si querés URLs fetcheables desde el browser sin firmar.

  2. Setear una policy de bucket. Los buckets públicos necesitan una policy SELECT que otorgue read access de objetos a anon. Los buckets privados no — el adapter mintea URLs firmadas short-lived vía presignDownload.

    Ejemplo de policy de public-read (correr en SQL Editor):

    create policy "public read avatars"
      on storage.objects for select
      to public
      using ( bucket_id = 'avatars' );
  3. Tomar la service-role key. Project Settings → API → Service role. Tratala como contraseña de database — sólo server, nunca el browser.

  4. Setear env vars.

    STORAGE_PROVIDER=supabase
    SUPABASE_URL=https://<ref>.supabase.co
    SUPABASE_SERVICE_ROLE_KEY=eyJhbGci...
    SUPABASE_STORAGE_BUCKET=avatars
  5. Reiniciá el server. El selector de storage adapter en apps/server/src/modules/storage/infrastructure/providers/index.ts levanta STORAGE_PROVIDER=supabase e instancia el adapter de Supabase; si falta alguna var requerida, loggea un warning y cae al provider null no-op para que la secuencia de boot igual complete.

5. Verificar

  • GET /healthz debería devolver 200.
  • GET /readyz debería reportar database: ok y storage: ok (el chequeo de storage pingea el bucket vía list(limit=1)).
  • Disparar un upload de avatar desde el client; el archivo aparece en Storage → <bucket> en Supabase.

Lo que deliberadamente saltamos

  • Supabase Auth. BetterAuth maneja sessions, OAuth, y magic-link en este boilerplate. Apilar Supabase Auth encima duplicaría esa surface. BetterAuth habla con Supabase Postgres vía Prisma como cualquier otro deployment de Postgres — sin wiring especial.
  • Realtime. Usá el server Socket.IO del boilerplate (ya cableado con adapter opcional de Redis para escalado horizontal).
  • Edge Functions. El trabajo de background pertenece a workers de BullMQ (apps/server/src/bootstrap/worker.ts).

Troubleshooting

  • prisma migrate deploy cuelga o tira error con "advisory lock" — estás apuntando las migrations al pooler. Confirmá que DIRECT_URL use el port 5432 (no 6543).
  • El upload de avatar devuelve 500 con bucket not found — el valor de SUPABASE_STORAGE_BUCKET no matchea un bucket en el proyecto, o la service-role key pertenece a un proyecto distinto.
  • La URL pública devuelve 403 — el bucket es privado y no agregaste una policy SELECT. O agregá la policy (arriba) o cambiá el client para fetchear vía la URL firmada que emite presignDownload.

En esta página