--- type: reference tags: [parking, dev-environment, workflow] sources: [] updated: 2026-06-15 --- # Local Dev Workflow > Dev-environment reference, not product architecture. How to run the stack locally and the > gotchas that have bitten us. For device testing under WSL also read [[wsl-dev-networking]]. ## First-time setup ```bash pnpm install cp apps/server/.env.example apps/server/.env # then fill in JWT_SECRET # JWT_SECRET=$(openssl rand -hex 32) # server refuses to start without a strong one pnpm --filter @parking/db exec drizzle-kit migrate # create the SQLite schema pnpm seed:admin # create the first admin (see [[local-jwt-auth]]) ``` `apps/server/.env` and the `*.sqlite` files are **gitignored** (local-only). Leave `NODE_ENV` **unset** in dev so the auth cookies aren't `Secure`-only (Vite dev is plain http). ## Running ```bash pnpm dev # turbo runs both: Vite (web, :5173) + Fastify (server, :3000) ``` Open `http://localhost:5173`. The Vite dev proxy forwards `/api` + `/health` to the backend, so the SPA and API are **same-origin** and the [[local-jwt-auth|cookie auth]] works without CORS. Production uses an **nginx** reverse proxy (`deploy/nginx.conf`) for the same same-origin setup. ## Gotchas (all fixed, recorded so they don't recur) - **Server dev must not be `node --experimental-strip-types src/index.ts`.** Type-stripping does **not** rewrite `.js` import specifiers to `.ts`, so it crashed with `ERR_MODULE_NOT_FOUND` and silently never started — the symptom was the SPA hanging for *minutes* (the Vite proxy waiting on a dead backend), then finally erroring. The `dev` script uses **`tsx watch`** instead. - **Vite proxy → `127.0.0.1`, not `localhost`.** `localhost` resolves to IPv6 `::1` first while the backend binds IPv4; Node's proxy can stall on the v6 attempt. Same class of "slow then works" hang, worse under WSL2 mirrored mode ([[wsl-dev-networking]]). - **`.env` must actually be loaded.** The server reads `process.env` only; the dev/start scripts load the file via Node's `--env-file-if-exists=.env`. An empty `JWT_SECRET=` makes the server fail-fast at boot. - **Seed into the DB the server reads.** `seed:admin` and the server must use the same `DATABASE_URL`; running via `pnpm seed:admin` (which loads `apps/server/.env`) keeps them aligned. ## Useful one-offs - First admin: `pnpm seed:admin` (prompts; blank username → `admin`). Non-interactive: `ADMIN_USER=.. ADMIN_PASS=.. pnpm seed:admin`. Reset a password: add `FORCE=1`. - Hardware test scripts (UHPPOTE): `apps/server/scripts/uhppote-listen.mjs` (live events), `uhppote-relay.mjs` (guarded door-open). See [[uhppote-controller]].