Supabase
Deploy this boilerplate against Supabase Postgres + Supabase Storage with the smallest possible config diff.
Supabase is a first-class deployment target for this boilerplate. You get Postgres + an S3-compatible storage bucket from a single vendor, and the boilerplate's existing Prisma + storage abstractions plug in without any code changes — only env vars.
This page covers the recommended path: Supabase as the database and file-storage backend, with BetterAuth running on top of Prisma against the Supabase Postgres instance. We do not use Supabase Auth — BetterAuth already owns the auth flows, sessions, and OAuth wiring in this codebase.
Out of scope: Supabase Realtime and Edge Functions. The boilerplate ships its own Socket.IO + BullMQ stack for those concerns.
1. Create a Supabase project
- Sign in at supabase.com and create a new project.
- Pick a strong database password (you will need it in step 2).
- Wait for the project to provision (~2 minutes).
2. Grab the connection strings
Open Project Settings → Database. You need two connection strings:
| Variable | Source (Supabase UI) | Why |
|---|---|---|
DATABASE_URL | "Connection pooling" → Transaction mode | Pooled (port 6543); used by the running app for short-lived queries |
DIRECT_URL | "Connection string" → URI | Direct (port 5432); used by prisma migrate for advisory locks |
Append ?pgbouncer=true&connect_timeout=15 to DATABASE_URL so Prisma knows
it is talking to a 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"Why two URLs? Supabase's pgbouncer pooler strips advisory locks and long-lived sessions, both of which Prisma migrations rely on. The runtime uses the pooled URL for connection efficiency; migrations use the direct URL. The
prisma/schema.prismadatasource dbblock already declaresdirectUrl = env("DIRECT_URL")for this reason.
3. Run migrations
From the repo root:
bunx prisma migrate deploy --schema apps/server/prisma/schema.prismaPrisma will use DIRECT_URL automatically. Verify the tables landed via
Table Editor in the Supabase UI.
4. (Optional) Switch storage to Supabase
If you want avatar uploads and any future file uploads to go to Supabase Storage instead of S3 / Cloudflare R2 / local disk:
-
Create a bucket. In Storage → New bucket, name it (e.g.
avatars). Mark it "Public" if you want browser-fetchable URLs without signing. -
Set a bucket policy. Public buckets need a
SELECTpolicy grantinganonread access to objects. Private buckets do not — the adapter mints short-lived signed URLs viapresignDownload.Example public-read policy (run in SQL Editor):
create policy "public read avatars" on storage.objects for select to public using ( bucket_id = 'avatars' ); -
Grab the service-role key. Project Settings → API → Service role. Treat it like a database password — server-only, never the browser.
-
Set env vars.
STORAGE_PROVIDER=supabase SUPABASE_URL=https://<ref>.supabase.co SUPABASE_SERVICE_ROLE_KEY=eyJhbGci... SUPABASE_STORAGE_BUCKET=avatars -
Restart the server. The storage adapter selector in
apps/server/src/modules/storage/infrastructure/providers/index.tspicks upSTORAGE_PROVIDER=supabaseand instantiates the Supabase adapter; if any required var is missing, it logs a warning and falls back to the no-op null provider so the boot sequence still completes.
5. Verify
GET /healthzshould return200.GET /readyzshould reportdatabase: okandstorage: ok(the storage check pings the bucket vialist(limit=1)).- Trigger an avatar upload from the client; the file appears under
Storage → <bucket>in Supabase.
What we deliberately skipped
- Supabase Auth. BetterAuth manages sessions, OAuth, and magic-link in this boilerplate. Stacking Supabase Auth on top would duplicate that surface. BetterAuth talks to Supabase Postgres via Prisma like any other Postgres deployment — no special wiring.
- Realtime. Use the boilerplate's Socket.IO server (already wired with optional Redis adapter for horizontal scaling).
- Edge Functions. Background work belongs in BullMQ workers
(
apps/server/src/bootstrap/worker.ts).
Troubleshooting
prisma migrate deployhangs or errors with "advisory lock" — you are pointing migrations at the pooler. ConfirmDIRECT_URLuses port5432(not6543).- Avatar upload returns 500 with
bucket not found— theSUPABASE_STORAGE_BUCKETvalue does not match a bucket in the project, or the service-role key belongs to a different project. - Public URL returns
403— bucket is private and you have not added aSELECTpolicy. Either add the policy (above) or switch the client to fetch via the signed URL emitted bypresignDownload.
Railway
Deploy the boilerplate (server + client + Postgres + Redis) to Railway in four services with config-as-code.
Polar.sh
Sell digital products via Polar.sh through the canonical IPaymentProvider port. Same architecture used to sell UseDeploy itself, ready for any boilerplate buyer to use for their own products.