The fastest path to a fully working local stack — no MongoDB Atlas account needed, just a local Mongo container. Written to be followed top to bottom without needing to know anything else about the repo first.
- Node.js and npm installed.
- OrbStack (or Docker Desktop — anything that gives you a working
dockerCLI) installed and running. If OrbStack is installed but not running:open -a OrbStack, then wait a few seconds fordocker infoto succeed.
One command, from the repo root:
docker compose -f docker-compose.local.yml up -dThis starts a mongo:7 container (karmacircle-local-mongo) on localhost:27017, with a named Docker volume so your data survives restarts. It's local-dev-only infra — not used in CI or production.
Confirm it's actually up:
docker exec karmacircle-local-mongo mongosh --quiet --eval "db.runCommand({ping:1})"Should print { ok: 1 }.
cd apps/api
npm install # skip if you've already done this
cp .env.example .envEdit apps/api/.env:
| Var | What to put |
|---|---|
MONGO_URI |
mongodb://localhost:27017/milan (points at the container from step 1) |
PORT |
5050 — not 5000, see Known gotchas |
CALLBACK_URL |
http://localhost:5050/auth/google/callback (must match PORT) |
JWT_SECRET |
Any random string — the example file's default is fine |
SECRET_KEY, CLIENT_ID, CLIENT_SECRET |
Must be non-empty even if you're not testing Google OAuth — any placeholder string works, e.g. "local-dev-placeholder". The example file ships these as "", which fails startup validation — see Known gotchas. |
RAZORPAY_KEY_ID, RAZORPAY_KEY_SECRET |
Same — must be non-empty, placeholder is fine unless testing payments |
Everything else (ORIGIN_URL, ORIGIN_DOMAIN, IGNORE_ORIGINS, successURL) |
Leave as the example file's defaults |
cd apps/web
npm install # skip if you've already done this
cp .env.example .envEdit apps/web/.env — the example file's VITE_MILANAPI var is a known dead-end (see docs/specs/known-issues.md); the code actually reads VITE_API_URL. Replace it:
VITE_API_URL="http://localhost:5050"
(5050 to match the backend's PORT from step 2.) VITE_RAZORPAY_KEY_ID/VITE_RAZORPAY_KEY_SECRET can stay empty or be placeholders unless you're testing the donate flow.
Two terminals (there's no unified dev orchestration wired up yet — each app runs its own npm run dev):
# terminal 1
cd apps/api && npm run dev
# → API is running on port 5050, logs "Connected to MongoDB"
# terminal 2
cd apps/web && npm run dev
# → Local: http://localhost:3000/curl http://localhost:5050/health
# {"status":"ok","mongo":"connected"}Then open http://localhost:3000 in a browser and try signing up / signing in — that exercises the full path (frontend → CORS → backend → Mongo → JWT cookie) in one go. http://localhost:5050/docs gives you Swagger UI if you want to hit backend routes directly without the frontend.
- Stop the dev servers:
Ctrl-Cin each terminal. - Stop Mongo (keeps your data for next time):
docker compose -f docker-compose.local.yml stop - Stop and delete Mongo's data:
docker compose -f docker-compose.local.yml down -v
- Port 5000 is often already taken on macOS by the system's AirPlay Receiver (
ControlCenterprocess) — you'll getEADDRINUSEeven though nothing in this repo is using it. Easiest fix: run the backend on a different port (this guide uses5050), rather than disabling AirPlay Receiver. Check what's squatting on a port withlsof -i :5000 -sTCP:LISTEN. apps/api/.env.exampleshipsSECRET_KEY/CLIENT_ID/CLIENT_SECRET/RAZORPAY_KEY_ID/RAZORPAY_KEY_SECRETas empty strings, butsrc/config/env.ts's Zod schema requires all of them non-empty (.min(1)) — the server won't boot at all until you fill in at least a placeholder for each, even if you have no intention of using Google OAuth or Razorpay locally.apps/web/.env.example'sVITE_MILANAPIvariable is not read by any code. The actual variable isVITE_API_URL. Following the example file literally gives you a frontend with no backend URL configured at all (silent failure, not an error).