A web-based calorie and nutrition tracking app with macro insights, activity logging, and 7-day trends.
Stack: Next.js 14 · TypeScript · Tailwind · Prisma · Supabase Auth · PostgreSQL · Upstash Redis · Recharts · TanStack Query · Zustand
- Node.js 20+
- Docker Desktop (for local Postgres + Redis)
- Supabase account (free tier) — for Auth
- Upstash account (optional) — for Redis caching
git clone <repo-url> calorieiq
cd calorieiq
npm installdocker compose up -dThis starts:
- PostgreSQL 16 on
localhost:5432(db:calorieiq, user/pass:postgres/postgres) - Redis 7 on
localhost:6379
cp .env.example .env.localThen fill in:
NEXT_PUBLIC_SUPABASE_URL,NEXT_PUBLIC_SUPABASE_ANON_KEY,SUPABASE_SERVICE_ROLE_KEY— from your Supabase project dashboardUPSTASH_REDIS_REST_URL,UPSTASH_REDIS_REST_TOKEN— optional; leave empty to disable Redis cachingDATABASE_URLandDIRECT_URL— point at your local Postgres (default values in.env.examplework with the Docker compose)
npm run db:push # Push the Prisma schema to your local DB
npm run db:seed # Seed 75+ foods + 50+ activitiesIn your Supabase dashboard:
- Authentication → Providers → Email — enable email/password
- Authentication → Providers → Google — enable Google OAuth and add a client ID
- Authentication → URL Configuration → Site URL — set to
http://localhost:3000 - Authentication → URL Configuration → Redirect URLs — add
http://localhost:3000/auth/callback
npm run devOpen http://localhost:3000.
calorieiq/
├── prisma/
│ ├── schema.prisma # Complete database schema
│ └── seed.ts # 75+ foods + 50+ activities
├── src/
│ ├── app/
│ │ ├── (auth)/ # /login, /register
│ │ ├── (app)/ # /dashboard, /history, /settings (protected)
│ │ ├── api/ # All backend routes
│ │ ├── auth/callback/ # OAuth callback
│ │ ├── layout.tsx # Root with Providers
│ │ └── page.tsx # Landing
│ ├── components/
│ │ ├── ui/ # Button, Card, Dialog, etc.
│ │ ├── auth/ # Login + Register forms
│ │ ├── dashboard/ # Calorie ring, macro bars, meal sections
│ │ ├── food/ # Food search modal + portion dialog
│ │ ├── activity/ # Activity logger
│ │ ├── history/ # Charts (Recharts)
│ │ ├── profile/ # Setup wizard
│ │ ├── settings/ # Settings view
│ │ └── layout/ # AppShell with sidebar + bottom nav
│ ├── contexts/ # auth.context.tsx
│ ├── stores/ # ui.store.ts (Zustand)
│ ├── hooks/ # useDashboard, useDebounce
│ ├── lib/
│ │ ├── calculations/ # tdee.ts, streak.ts (pure functions)
│ │ ├── food/ # search orchestrator + OFF client
│ │ ├── api/ # response helpers + createHandler
│ │ ├── auth/session.ts # getAuthUser
│ │ ├── prisma.ts # singleton client
│ │ ├── supabase.ts # browser + server + admin clients
│ │ └── redis.ts # lazy Upstash client
│ └── validation/schemas.ts # All Zod schemas
├── tests/
│ └── unit/ # Vitest tests for tdee, streak, schemas
├── middleware.ts # Edge auth guard
└── docker-compose.yml # Local Postgres + Redis
| Command | What it does |
|---|---|
npm run dev |
Start Next.js dev server |
npm run build |
Production build |
npm run start |
Start production server |
npm run lint |
Run ESLint |
npm run type-check |
TypeScript check |
npm run test |
Run Vitest unit tests |
npm run test:watch |
Watch mode |
npm run db:push |
Push Prisma schema (no migration files) |
npm run db:migrate |
Create and apply migration |
npm run db:seed |
Seed foods + activities |
npm run db:studio |
Open Prisma Studio at localhost:5555 |
- Visit
/→ click Get Started - Register with email + password → Supabase creates auth user → row inserted into
userstable - Redirected to
/profile/setup→ 3-step wizard (basic info → goal → activity level) - TDEE calculated and stored in
user_profiles.calorie_target - Lands on
/dashboardshowing calorie ring (empty), macro bars, 4 empty meal sections - Click + Add in Breakfast → Food Search Modal opens
- Search "oats" → tap result → adjust quantity → confirm
- Dashboard updates immediately (optimistic) → ring fills → macros progress
- Click + Add Activity → search "running" → enter duration → log
- Dashboard now shows net calories with burn applied
- Visit
/history→ 7-day charts (will be empty for first day, fills out across days) - Visit
/settings→ adjust macros, change weight, override calorie target
- Optimistic mutations for food/activity logging via TanStack Query — UI updates instantly with snapshot rollback on error
- Multi-layer food search — Redis (60s TTL) → Postgres ILIKE → Open Food Facts API in parallel (400ms hard timeout)
- Pure TDEE engine in
lib/calculations/tdee.ts— Mifflin-St Jeor BMR + activity multiplier + goal adjustment, 1,200 kcal floor - Edge middleware guards
/dashboard,/log,/history,/profile,/settings— redirects unauth users - Cascade deletes on
Userenable GDPR right-to-erasure with a singleDELETE FROM users WHERE id = ?
GET /api/health returns 200 if Postgres + Redis are reachable, 503 otherwise. Use for uptime monitors.
- PRD-CalorieIQ.md — product requirements
- TECHNICAL_ARCHITECTURE.md — architecture and design decisions
- WIREFRAMES.md — UI wireframes for all 9 screens
- ENGINEERING_TASKS.md — 142-task breakdown across 6 weeks
Error: Environment variable not found: DATABASE_URL
Make sure .env.local exists. Restart the dev server after editing.
Auth redirects loop or 401 on every API call
Verify your Supabase URL + keys, and that the Site URL in Supabase auth settings matches http://localhost:3000. Clear cookies and try again.
Food search returns nothing
The seed script needs to have run. Check npm run db:studio — the foods table should have ~75 rows. Re-run npm run db:seed.
Open Food Facts results never appear
The OFF client has a hard 400ms timeout — slow networks fall back to local-only. This is intentional. To verify OFF is reachable, hit https://world.openfoodfacts.org/cgi/search.pl?search_terms=apple&json=true in your browser.