Skip to main content

Configuration pattern

How we define and load application configuration so backend and frontends share a single root .env while each app uses a single, consistent set of variable names in code.

Overview

  • Single source for local dev: One root .env at the monorepo root holds all env vars. Backend and frontend apps load from this file so developers edit one place.
  • Prefixed names in root: In the root .env, names are prefixed by app. Backend vars use BACKEND_ (e.g. BACKEND_PORT, BACKEND_DATABASE_URL; no BACKEND_HOST in root—host is localhost in dev). Desktop frontend vars use FRONTEND_DESKTOP_ for frontend-specific vars (e.g. FRONTEND_DESKTOP_PORT for the dev server port). VITE_BACKEND_URL is not set in root; the desktop Vite config derives it as http://localhost:BACKEND_PORT only when unset (see below).
  • Strip or map into app names: When loading, the backend strips the BACKEND_ prefix so app code only sees unprefixed names (PORT, DATABASE_URL). The frontend maps FRONTEND_DESKTOP_ to VITE_* so app code only uses import.meta.env.VITE_BACKEND_URL etc. App code never references the root prefixed names; no dual naming.
  • Deployment: In deployed environments the platform injects vars directly (backend: e.g. PORT; frontend: e.g. VITE_BACKEND_URL at build time). Root .env is for local development; optional overrides can live in app-level .env files (backend: unprefixed; frontend: VITE_* if desired).

Root .env convention

In root .envBackend seesFrontend (desktop) sees
BACKEND_PORT=3000process.env.PORT
BACKEND_DATABASE_URL=...process.env.DATABASE_URL
FRONTEND_DESKTOP_PORT=5173dev server uses VITE_PORT (server.port); mapped automatically via FRONTEND_DESKTOP_*VITE_*
FRONTEND_DESKTOP_* (other vars)import.meta.env.VITE_*

Dev vs deployed API URL: In dev, VITE_BACKEND_URL is derived by the desktop Vite config as `http://localhost:BACKEND_PORT` only when VITE_BACKEND_URL is undefined (the dev script sets BACKEND_PORT and FRONTEND_DESKTOP_PORT only). In deployed builds the platform sets VITE_BACKEND_URL at build time (e.g. /api for same-origin); the config does not override when already set. No BACKEND_HOST or BACKEND_URL in root.

Root .env.example documents these prefixed vars. Backend and frontend stack rules (configuration) describe how each app loads and exposes them.

Backend

Backend loads root .env then backend/.env via ConfigModule envFilePath (see getEnvFilePaths() in backend config). The validate function (mapAndValidate in env.validation.ts) maps BACKEND_* to unprefixed keys in the config object, then validates with the schema, so app code sees PORT, GOOGLE_CLIENT_ID, etc. See stack rule configuration (backend).

Frontend

Desktop (and any other frontend) sets Vite envDir to the monorepo root. At config time it loads root .env, maps keys starting with FRONTEND_DESKTOP_ to VITE_* (so FRONTEND_DESKTOP_PORTVITE_PORT for the dev server), and sets VITE_BACKEND_URL to `http://localhost:BACKEND_PORT` only when VITE_BACKEND_URL is undefined (dev default; deployed sets it at build time). Code uses only import.meta.env.VITE_*. See stack rule configuration (frontend) and vite.

Implementation

  • Root: boilerplate/.env.example (and optionally .gitignore for .env). Backend implements map and validate via ConfigModule envFilePath and validate: mapAndValidate (see backend configuration); frontend implements map in desktop Vite config.
  • Pattern environments can use the same root and prefixes (e.g. BACKEND_ / FRONTEND_DESKTOP_ for env name if desired) and remains consistent with this pattern.

References

  • Stack: configuration (backend), configuration (frontend), vite, hosting.
  • Pattern: environments (environment detection vars).