Quickstart
Get both halves of TALA running against a local Postgres, including the connection traps that cost the most time.
Ten minutes to a working local stack, assuming you already have Postgres and the credentials.
Prerequisites
- Node v22.16.0 — pinned in
backend/.nvmrc. Newer works; older does not. - PostgreSQL running locally, or a Supabase connection string.
- AWS S3 bucket, CloudFront distribution, and Lambda credentials.
- Resend API key, plus
MAIL_FROM_NAMEandMAIL_FROM_EMAILon a verified domain. - Google and GitHub OAuth client credentials — optional; everything else works without them.
Backend
Copy the environment file
cd backend
cp .env.example .env.env.example is the authoritative reference for every variable, and it documents the connection-string traps below. Read it before debugging a database failure.
Fill in the required values
Two variables are checked at boot, and the app refuses to start if either fails:
SECRET_KEY— at least 32 characters, and must not containchange-me..env.exampleships exactly that placeholder, so you must replace it or the boot check fails.BUFFER_KEY— exactly 64 hex characters.
DATABASE_URL is not part of that check but is obviously required; a bad one fails at the first query instead of at boot. Add the AWS, Resend, and OAuth credentials as you need them.
# Both of these must be replaced, not left as shipped.
SECRET_KEY=$(openssl rand -hex 32)
BUFFER_KEY=$(openssl rand -hex 32)A Homebrew-default local Postgres is built without SSL, so it also needs:
DB_SSL=falseInstall, migrate, run
npm install
npm run migration:run
npm run start:devThe API listens on port 8000. Swagger UI is at http://localhost:8000/api-docs and the liveness probe at http://localhost:8000/health — note that /health sits outside the api global prefix.
Frontend
cd frontend
echo "VITE_API_URL=http://localhost:8000" > .env
npm install
npm run dev
Vite serves on http://localhost:5173, which is also the CORS fallback origin the backend allows when FRONTEND_URL is unset.
Supabase connection traps
Shared environments run on Supabase. Use the session pooler, never the direct host:
postgresql://postgres.<ref>:<password>@aws-1-<region>.pooler.supabase.com:5432/postgres
The direct host is IPv6-only
db.<ref>.supabase.co publishes an AAAA record and no A record. It is unroutable from most laptops and IPv4 hosts unless you pay for the IPv4 add-on.
Port 6543 breaks TypeORM
5432 is session mode and is the one you want. 6543 is transaction mode, which breaks TypeORM’s prepared statements.
The username is not plain postgres
It is postgres.<project-ref>. Newer projects also use the aws-1- region prefix, not aws-0-.
Never set DB_SSL=false against Supabase
That flag is a local-Postgres-only escape hatch for a server built without SSL. Against a hosted database it disables transport encryption outright.
Row-level security is not automatic
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 any of this, so tables created by a migration land wide open.
The LockDownPublicSchema migration enables RLS on every public table and revokes those roles’ grants and default privileges.
Regenerating the API reference
The API reference in these docs is built from docs/openapi.json, which is a snapshot of the backend’s live Swagger document. Refresh it whenever a controller changes:
cd backend && npm run start:dev # terminal 1
cd docs && npm run spec # terminal 2
Then rebuild the docs:
cd docs && npm run build
