Skip to content
← All docs

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.

VariableRequiredDefaultDescription
NODE_ENVnodevelopmentdevelopment, test, or production
PORTno4000Port the API listens on
CORS_ORIGINSnohttp://localhost:3000Comma-separated list of allowed browser origins (the marketing frontend)
TRUST_PROXYno1Reverse-proxy hops for client IP / X-Forwarded-Proto (a number, true/false, or a subnet)
FORCE_HTTPSnofalseForce the HTTP→HTTPS redirect outside production (always on in production)
DATABASE_URLyes (prod)postgresql://leadfella:leadfella@localhost:5432/leadfella_sitePostgreSQL connection string. For TLS add ?sslmode=require (or ?sslmode=no-verify for self-signed managed certs)
ADMIN_EMAILnoadmin@leadfella.comThe single admin account's email
ADMIN_PASSWORD_HASHnobcrypt hash of the admin password. If blank (and no DB/plaintext password), login uses the admin123 first-run default
ADMIN_PASSWORDnoPlaintext admin password (development only; prefer the hash or the in-panel change)
JWT_SECRETyes (prod)dev-insecure-secret-change-meSecret used to sign admin tokens. Rotating it invalidates all admin sessions
JWT_TTL_HOURSno12Admin token lifetime in hours
RATE_LIMIT_WINDOW_MSno60000Rate-limit window in milliseconds
RATE_LIMIT_MAXno60Max 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, and ADMIN_PASSWORD_HASH in production.
  • Public submit endpoints (waitlist/contact/login) use a tighter limiter (5 requests/minute) on top of the general limit.
  • The .env.example ships with .../leadfella_site as 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:

  1. A password set from inside the admin panel (stored hashed in the database - this wins and survives restarts).
  2. ADMIN_PASSWORD_HASH (bcrypt) if set.
  3. ADMIN_PASSWORD (plaintext, dev only) if set.
  4. 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.

VariableDefaultDescription
NEXT_PUBLIC_API_URLhttp://localhost:4000Base URL of the website API
NEXT_PUBLIC_SITE_URLhttps://leadfella.comCanonical site URL (used in SEO metadata)
NEXT_PUBLIC_APP_URLhttps://app.leadfella.comURL 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_URL points at the production PostgreSQL (with sslmode as needed).
  • JWT_SECRET is a long random string, unique to production.
  • ADMIN_PASSWORD_HASH is set (not ADMIN_PASSWORD).
  • CORS_ORIGINS lists exactly your production frontend origin(s).
  • NEXT_PUBLIC_API_URL points at https://api.leadfella.com.

See deployment.md for shipping to production.