Supabase Development Guide

Environment Files

FileTargetDescription
functions/.env.localLocal Supabaselocalhost:54321/54322, deterministic demo keys
functions/.env.devRemote devProject jfmdrdleyyclgqjjowxt (eu-west-1)
functions/.env.prodRemote prodProject aqbchetcfyqtyrdjznvz (eu-central-1)
functions/.env.exampleTemplateAll required vars with placeholders

No bare .env file. All commands require explicit --env-file to prevent accidental env mix-ups.


Local Development

1. Start Supabase local stack

pnpm supabase:start
# or from supabase/: pnpm start

This starts Postgres, Auth, Storage, Realtime, Studio and applies all migrations.

2. Serve the API function

pnpm --filter @scalex/supabase fn:serve:local

3. Seed the database

Requires the function to be running (step 2).

pnpm --filter @scalex/supabase seed:local

This calls POST /api/admin/seed-database with clean strategy (wipes and re-inserts all seed data).

4. Access Swagger UI


Remote Dev Environment Setup

First-time setup

# 1. Push schema migrations to the dev project
pnpm --filter @scalex/supabase deploy:dev
 
# 2. Seed the remote dev database via local function serve
#    Terminal 1 — serve functions locally, pointing at remote dev DB:
pnpm --filter @scalex/supabase fn:serve:dev
 
#    Terminal 2 — seed through the local serve (writes to remote dev DB):
pnpm --filter @scalex/supabase seed:dev:local

Why seed:dev:local instead of seed:dev? seed:dev:local calls the local function URL (127.0.0.1:54321) while fn:serve:dev is running with .env.dev. This means the function writes to the remote dev DB without needing to deploy the function first. Use seed:dev only after the function is deployed to the remote project.

After deploying the function to remote dev

# Deploy function and set secrets
supabase link --project-ref jfmdrdleyyclgqjjowxt
supabase functions deploy api --no-verify-jwt
supabase secrets set --env-file functions/.env.dev
 
# Seed directly via remote function
pnpm --filter @scalex/supabase seed:dev

Day-to-day dev against remote

# Serve functions locally, pointing at remote dev DB
pnpm --filter @scalex/supabase fn:serve:dev

Schema Migrations (Drizzle code-first)

Schema is defined in packages/db/schema/ using Drizzle ORM.

# 1. Edit schema in packages/db/schema/
# 2. Generate migration SQL
pnpm db:generate
 
# 3. Apply locally
supabase db reset
 
# 4. Apply to remote dev
pnpm --filter @scalex/supabase migrate:dev

Seeding

Seeding is separated from schema migrations by design:

  • Schema: applied via supabase db push (migration files)
  • Seed data: applied via POST /api/admin/seed-database (API endpoint)

This allows seeding any environment (local, dev, prod) without destructive db reset.

Strategies

StrategyBehaviorUse case
cleanDeletes all data, then insertsFresh start, local dev
upsertUpdates existing by ID, inserts newIncremental, safe for shared envs

Seed data

Defined in functions/api/lib/seeding/data/:

  • 25 Ukrainian oblasts, 10 locations, 10 scales with cameras
  • 2 test customers (Clerk orgs), 2 contacts
  • 3 sample orders with payments, receipts, photos
  • Scale commands, sessions, heartbeats, weight logs
  • App remote configs

Seeding respects FK dependencies (21 tables in order).


"secrets:dev": "supabase link --project-ref jfmdrdleyyclgqjjowxt && supabase secrets set --env-file functions/.env.dev",

Available Scripts (supabase/package.json)

ScriptDescription
startStart local Supabase (if not running)
resetReset local DB (migrations + seed.sql)
fn:serve:localServe functions against local Supabase
fn:serve:devServe functions against remote dev
fn:serve:prodServe functions against remote prod
seed:localSeed local DB via local function (requires fn:serve:local)
seed:dev:localSeed remote dev DB via local function (requires fn:serve:dev)
seed:devSeed remote dev DB via deployed remote function
deploy:devDeploy functions to dev project
migrate:devLink to dev project and push migrations
secrets:devSet secrets to dev project
testRun Supabase DB tests
lintLint SQL

Root scripts (turbo delegations)

ScriptDescription
supabase:startturbo start --filter @scalex/supabase
supabase:typesGenerate TS types from remote schema
supabase:db:resetReset local database
db:generateGenerate Drizzle migration SQL

Notes

  • --no-verify-jwt skips JWT auth check for local/dev testing
  • Swagger UI auto-injects x-admin-key header from ADMIN_API_KEY env var
  • Local Supabase keys are deterministic demo values (safe to commit)
  • Clerk third-party auth is configured in config.toml
  • Storage bucket weight-photos is auto-created locally via config.toml

Why DATABASE_URL uses db.supabase.internal:5432 locally

The Edge Runtime runs inside Docker (supabase_edge_runtime_hono container). From within the container, localhost refers to the container itself — not the host machine. All Supabase local services share the supabase_network_hono Docker network, so the Postgres container is reachable via its Docker alias db.supabase.internal on its internal port 5432 (not the host-exposed 54322).