Configuration
All configuration is environment-based. Secrets are provided via env only and
must never be committed. *.env files are gitignored (except *.env.example).
Backend (website/backend/.env)
Copy .env.example to .env and fill in the values.
| Variable | Required | Default | Description |
|---|---|---|---|
NODE_ENV | no | development | development, test, or production |
PORT | no | 4000 | Port the API listens on |
CORS_ORIGINS | no | http://localhost:3000 | Comma-separated list of allowed browser origins (the marketing frontend) |
TRUST_PROXY | no | 1 | Reverse-proxy hops for client IP / X-Forwarded-Proto (a number, true/false, or a subnet) |
FORCE_HTTPS | no | false | Force the HTTP→HTTPS redirect outside production (always on in production) |
DATABASE_URL | yes (prod) | postgresql://leadfella:leadfella@localhost:5432/leadfella_site | PostgreSQL connection string. For TLS add ?sslmode=require (or ?sslmode=no-verify for self-signed managed certs) |
ADMIN_EMAIL | no | admin@leadfella.com | The single admin account's email |
ADMIN_PASSWORD_HASH | no | – | bcrypt hash of the admin password. If blank (and no DB/plaintext password), login uses the admin123 first-run default |
ADMIN_PASSWORD | no | – | Plaintext admin password (development only; prefer the hash or the in-panel change) |
JWT_SECRET | yes (prod) | dev-insecure-secret-change-me | Secret used to sign admin tokens. Rotating it invalidates all admin sessions |
JWT_TTL_HOURS | no | 12 | Admin token lifetime in hours |
RATE_LIMIT_WINDOW_MS | no | 60000 | Rate-limit window in milliseconds |
RATE_LIMIT_MAX | no | 60 | Max requests per window for general traffic |
Notes:
- The configuration is validated once at boot (with
zod); a bad or missing value fails fast with a clear message. - The dev defaults let the API boot with zero configuration, but they are
insecure - always set a real
DATABASE_URL,JWT_SECRET, andADMIN_PASSWORD_HASHin production. - Public submit endpoints (waitlist/contact/login) use a tighter limiter (5 requests/minute) on top of the general limit.
- The
.env.exampleships with.../leadfella_siteas an example database name; use whatever database you actually created (the value is what matters).
Admin credential model
Admin auth is single-user. Set ADMIN_EMAIL, then a password resolves in this
order:
- A password set from inside the admin panel (stored hashed in the database - this wins and survives restarts).
ADMIN_PASSWORD_HASH(bcrypt) if set.ADMIN_PASSWORD(plaintext, dev only) if set.- Otherwise a first-run default of
admin123- so a fresh deploy can sign in immediately. The dashboard shows a warning while this default is active.
After the first login, open the dashboard and use Change password to set your
own (it persists to the database and overrides the env). To rotate the password
without the panel, set ADMIN_PASSWORD_HASH (npm run seed:admin -- "<password>")
and clear the stored one. Rotating JWT_SECRET invalidates all active sessions.
Frontend (website/frontend)
The frontend reads NEXT_PUBLIC_* variables at build time. Set them in
website/frontend/.env.local for development or in your host's environment.
| Variable | Default | Description |
|---|---|---|
NEXT_PUBLIC_API_URL | http://localhost:4000 | Base URL of the website API |
NEXT_PUBLIC_SITE_URL | https://leadfella.com | Canonical site URL (used in SEO metadata) |
NEXT_PUBLIC_APP_URL | https://app.leadfella.com | URL of the product app (linked from the site) |
Because these are NEXT_PUBLIC_*, they are inlined into the client bundle - put
no secrets here. They are configuration, not credentials.
Production checklist
DATABASE_URLpoints at the production PostgreSQL (withsslmodeas needed).JWT_SECRETis a long random string, unique to production.ADMIN_PASSWORD_HASHis set (notADMIN_PASSWORD).CORS_ORIGINSlists exactly your production frontend origin(s).NEXT_PUBLIC_API_URLpoints athttps://api.leadfella.com.
See deployment.md for shipping to production.