A standard Nest module layout (words, user, auth, supabase, config, common). Details
and environment variables are in apps/backend/README.md.
SupabaseService is the only place clients are created:
getAuthClient()— verifies tokens;getClientForUser(token)— returns an RLS-scoped client for the caller.
There is deliberately no service-role key. A service-role client would bypass RLS on every request.
Auth is a global AuthGuard (opt out with @Public()) that attaches { id, email, accessToken },
read in handlers via @CurrentUser().
Keep the provider graph free of Scope.REQUEST, and never authenticate inside a provider
factory. A request-scoped Supabase client used to throw UnauthorizedException from its factory;
because Nest resolves request-scoped providers before guards run, that throw short-circuited the
guard chain and the global ThrottlerGuard never executed on protected routes. The 401 looked
correct, so nothing surfaced it. Covered by apps/backend/test/rate-limit.e2e.test.ts.
Environment is validated at startup (src/config/env.validation.ts): missing vars exit the process
with a readable message instead of failing on the first request.
generate-types(inapps/backend) regeneratesdatabase.types.tsand copies it intopackages/api. Do not edit those files (they are eslint-ignored, solint:fixcannot reformat them).- The API contract is generated separately — see api-contract.md.
- Backend
lintis check-only; uselint:fixto autofix (same split as every other package).