Environments
Three environments mapped onto three branches, the TLS rules for each, and the Supabase connection traps and RLS lockdown that are not optional.
| dev | staging | prod | |
|---|---|---|---|
| Branch | dev |
staging |
main |
| Database | local Postgres tala_dev |
Supabase (shared) | Supabase — not created as of 2026-09-09 |
DB_SYNCHRONIZE |
may be true locally |
must be set to false |
forced false by NODE_ENV |
migrationsRun |
false |
false |
false |
Migrations are run explicitly in every environment. Nothing runs them at boot.
Environment validation
The backend validates its environment at startup via validateEnv and refuses to boot on a bad one. It checks exactly two variables:
| Variable | Rule |
|---|---|
SECRET_KEY |
at least 32 characters, and must not match /change-?me/i |
BUFFER_KEY |
exactly 64 hex characters |
DATABASE_URL is not validated — a missing or malformed connection string fails later, at the first query, not at boot.
.env.example is the authoritative reference for everything else — read it before debugging a connection.
TLS to the database
if (this._configService.get<string>("DB_SSL") === "false") return false;
DB_SSL=false is a local-Postgres-only escape hatch, for a Homebrew build compiled without SSL.
Outside production the connection also uses ssl.rejectUnauthorized: false. That is a permissive TLS path intended for local development, but it applies to any non-production connection — including a staging connection to a hosted database. On staging that means the traffic is encrypted but the database’s certificate is never verified, so a substituted certificate would be accepted. Narrowing the flag to local Postgres is unowned.
In production, DB_SSL_CA may pin a CA as either an inline PEM or a path to one. If DB_SSL_CA is unset, the certificate is still validated against Node’s bundled CAs and a warning is logged.
Supabase
Use the session pooler, never the direct host:
postgresql://postgres.<ref>:<password>@aws-1-<region>.pooler.supabase.com:5432/postgres
| Trap | Detail |
|---|---|
| Direct host is IPv6-only | db.<ref>.supabase.co has AAAA and no A record — unroutable from most laptops without the paid IPv4 add-on |
| Port | 5432 is session mode. 6543 is transaction mode and breaks TypeORM’s prepared statements |
| Username | postgres.<project-ref>, not postgres |
| Region prefix | newer projects use aws-1-, not aws-0- |
DB_SSL=false |
Never against Supabase — it disables transport encryption outright |
Row-level security
Supabase publishes the public schema through PostgREST and grants anon and authenticated full DML on everything created there. The anon key is public by design. TypeORM knows nothing about RLS, so tables created by a migration land wide open.
LockDownPublicSchema enables RLS on all public tables and revokes those roles’ grants and their default privileges. The default privileges are the part people skip: leave them in place and the next table a migration creates re-opens the hole.
