Configuration
All API configuration is environment variables, validated at boot against a
schema (src/config/env.schema.ts). If a required value is missing or malformed,
the API prints every problem and exits before serving a single request.
Environment variables
| Variable | Required | Purpose |
|---|---|---|
DATABASE_URL | yes | PostgreSQL connection string (postgres://user:pass@host:5432/db). |
BETTER_AUTH_SECRET | yes | Signing key for sessions and tokens. Must be at least 16 characters. |
ENCRYPTION_KEY | yes | Base64-encoded 32-byte key for encrypting sensitive columns at rest. |
FRONT_URL | yes | Frontend origin. Used for CORS and as the passkey relying-party ID. |
BETTER_AUTH_URL | no | Public URL of the API, used in auth flows and email links. |
API_URL | no | Public URL of the API, referenced in outbound email. |
SMTP_HOST | prod | SMTP server hostname. |
SMTP_PORT | prod | SMTP port (typically 587). |
SMTP_USER | prod | SMTP username. |
SMTP_PASS | prod | SMTP password. |
EMAIL_FROM | prod | From address for transactional email. Must be a bare address (no-reply@example.com); a display-name form is rejected. |
NODE_ENV | no | development, production, or test. |
PORT | no | HTTP port the API listens on. Defaults to 3000. |
The Postgres container additionally reads POSTGRES_USER, POSTGRES_PASSWORD,
and POSTGRES_DB from the same file.
“prod” means the value is optional in development and test, but required when NODE_ENV=production - email sending is mandatory in production, so the API
refuses to start without working SMTP settings.
Generating secrets
# BETTER_AUTH_SECRET - any string >= 16 chars
openssl rand -base64 24
# ENCRYPTION_KEY - must decode to exactly 32 bytes
openssl rand -base64 32 ENCRYPTION_KEY is checked precisely: the API base64-decodes it and rejects
anything that is not 32 bytes. Keep it safe and stable - it decrypts existing
rows, so rotating it requires re-encrypting your data.
Encryption at rest
Sensitive user columns (name, email, and session IP / user-agent) are encrypted
with ENCRYPTION_KEY before they hit the database. Email is encrypted
deterministically so it stays unique and searchable. If you introduce the key on
an existing instance that has plaintext rows, backfill them once:
pnpm data-encrypt Frontend API URL
The Angular frontend is a static bundle, so its API endpoint is fixed at build
time rather than read from the environment at runtime. Before building rollnote-front, point it at your API in src/environments/environment.ts:
export const environment = {
production: true,
apiUrl: 'https://api.example.com'
}; The development config (environment.development.ts) defaults to http://localhost:3000. Because auth is cookie-based, the API’s FRONT_URL must
exactly match the origin you serve the frontend from, or CORS will reject the
requests.
Backups
Everything lives in Postgres. Back it up with pg_dump:
# Single quotes: the variables expand inside the container, where they are set.
docker compose exec -T postgres
sh -c 'pg_dump -U "$POSTGRES_USER" "$POSTGRES_DB"' > rollnote-backup.sql Restore into a fresh database with psql. Keep your ENCRYPTION_KEY alongside
the dump - without it the encrypted columns cannot be read back.
Upgrading
git -C rollnote-api pull
git -C rollnote-front pull
docker compose up --build -d The API applies any new migrations on boot, then re-seeds global reference data idempotently, so upgrades are safe to run repeatedly. Take a backup first.