An AI-powered nutrition logging web app. Just say what you ate!
demo.mp4
YouTube video demo - In better quality
This project aims to track 48+ total nutrients using a centralized definition system:
Adding nutrients is now super easy! See Nutrient System Documentation
See the full list of defined nutrients here.
Quick Add Example:
# Add to config/nutrients.yml
- key: taurine_mg
type: float64
unit: mg
category: amino_acid
per_100g: true
original: true
go_field_name: Taurine
json_tag: taurine_mg
display_name: TaurineThen run: script/generate-nutrients β All 20+ files automatically updated!
This diagram shows the simplified user journey from input to saved meal data, focusing on the user experience and key business logic rather than technical implementation details. The full technical flow is documented separately here.
graph TB
%% User journey starts - Multiple input methods
Start([User Input Options:<br/>π€ Voice Recording<br/>βοΈ Text Description<br/>β Duplicate Favorite]) --> Auth{User Authentication<br/>JWT or API Key}
%% Authentication - simplified
Auth -->|β
Authenticated User| AuthOK[User Verified<br/>JWT or Pro API Key]
Auth -->|β Not Logged In| AuthFail[Login Required]
AuthFail --> End([End])
%% Input processing flow
AuthOK --> InputMethod{Input Method?}
%% Three input paths
InputMethod -->|π€ Voice| AudioProcess[Audio Upload & Transcription<br/>Max 50MB, AI converts speech to text]
InputMethod -->|βοΈ Text| TextProcess[Direct Text Processing<br/>Skip transcription step]
InputMethod -->|β Duplicate| DuplicateProcess[Copy Existing Favorite<br/>Skip AI processing entirely]
%% Error handling
AudioProcess -->|β Invalid File| UploadError[Error: Invalid Audio File]
TextProcess -->|β Empty Text| TextError[Error: Text Required]
DuplicateProcess -->|β Not Found| DuplicateError[Error: Favorite Not Found]
UploadError --> End
TextError --> End
DuplicateError --> End
%% Success paths converge
AudioProcess -->|β
Transcribed| AIProcess[AI Processing Pipeline]
TextProcess -->|β
Text Ready| AIProcess
DuplicateProcess -->|β
Copied| SaveDirect[Save Duplicated Meal]
%% AI Processing - simplified into logical steps
AIProcess --> Step1[Step 1: Extract Food Items<br/>AI identifies individual foods from text]
Step1 -->|β
Foods Identified| Step2[Step 2: Get Nutrition Data<br/>Smart lookup with AI assistance]
Step1 -->|β Failed| ProcessError[Error: Could not understand input]
ProcessError --> End
%% Smart nutrition lookup - business logic
Step2 -->|β
Success| NutritionLookup{How do we find nutrition data?}
Step2 -->|β Failed| ProcessError
%% Different data sources - simplified
NutritionLookup --> Cache[Check Our Database<br/>for Previously Calculated Foods]
NutritionLookup --> ProductDB[Search External Product Databases<br/>for Branded Items]
NutritionLookup --> AI[Ask AI for Nutrition<br/>Analysis and Estimates]
Cache -->|Found| UseCache[β
Use Cached Data<br/>Fast Response]
Cache -->|Not Found| ProductDB
ProductDB -->|Found Brand Match| UseProduct[β
Use Product Data as Context<br/>AI gets real product info for<br/>more accurate nutrition calculation]
ProductDB -->|Not Found| AI
AI --> UseAI[β
AI Calculates Nutrition<br/>Comprehensive Analysis]
%% All paths lead to nutrition data
UseCache --> NutritionReady[Nutrition Data Ready<br/>for All Food Items]
UseProduct --> NutritionReady
UseAI --> NutritionReady
%% Final steps - user value
NutritionReady --> Summary[Calculate Meal Summary<br/>48+ Total Nutrients including<br/>Calories, Protein, Vitamins, Minerals]
Summary --> Save[Save to User's Meal History<br/>with Labels and Timestamps]
SaveDirect --> DirectSuccess[Return Duplicated Meal<br/>π± User sees copied nutrition data]
%% Success response
Save -->|β
Saved Successfully| Success[Return Complete Results<br/>π± User sees comprehensive nutrition data]
Save -->|β οΈ Save Failed| PartialSuccess[Return Nutrition Data<br/>β οΈ Not saved to history]
Success --> UserActions[User Can:<br/>π View on Dashboard<br/>π Add to Log<br/>β Mark as Favorite<br/>π·οΈ Add Labels]
PartialSuccess --> UserActions
DirectSuccess --> UserActions
UserActions --> End
%% Key benefits callout
NutritionReady --> Benefits[Key Benefits:<br/>π Fast responses via 4-tier caching<br/>π― Real product data guides AI decisions<br/>π€ AI fills gaps for everything else<br/>π 48+ comprehensive nutrients tracked<br/>β Easy favorites and duplication<br/>π·οΈ Flexible labeling system]
%% Styling for business audience - High contrast for accessibility
classDef userAction fill:#0D47A1,stroke:#000000,stroke-width:3px,color:#FFFFFF
classDef success fill:#1B5E20,stroke:#000000,stroke-width:3px,color:#FFFFFF
classDef error fill:#B71C1C,stroke:#000000,stroke-width:3px,color:#FFFFFF
classDef process fill:#E65100,stroke:#000000,stroke-width:3px,color:#FFFFFF
classDef benefit fill:#4A148C,stroke:#000000,stroke-width:3px,color:#FFFFFF
classDef input fill:#6A1B9A,stroke:#000000,stroke-width:3px,color:#FFFFFF
class Start userAction
class AuthOK,UseCache,UseProduct,UseAI,Success,PartialSuccess,DirectSuccess,SaveDirect success
class AuthFail,UploadError,TextError,DuplicateError,ProcessError error
class AudioProcess,TextProcess,DuplicateProcess,AIProcess,Step1,Step2,Summary,Save process
class Benefits,UserActions benefit
class InputMethod input
-
Setup Dependencies:
script/bootstrap
-
Get API Keys:
- OpenAI API key from platform.openai.com
-
Configure:
# create .env file # Required OPENAI_API_KEY=<key> # Server Configuration - Use port 3001 for API to avoid conflict with SvelteKit frontend PORT=3001 ENV=development # CORS Configuration - Allow SvelteKit frontend CORS_ALLOWED_ORIGINS=http://localhost:3000 # Logging & Debugging (Enable detailed error reporting) LOG_LEVEL=DEBUG DEBUG=true # OpenAI Configuration OPENAI_TRANSCRIBE_MODEL=gpt-4o-mini-transcribe TRANSCRIBE_LANGUAGE=en #OPENAI_TRANSCRIBE_RESPONSE_FORMAT= # Upload Configuration MAX_UPLOAD_BYTES=104857600 # Maximum upload size in bytes (default: 100MB) # Database Configuration - Use Supabase for local and production # Follow Supabase local development setup: https://supabase.com/docs/guides/local-development
-
Run the API:
PORT=3001 script/server(use--productionfor production mode).
-
Setup Dependencies:
script/bootstrap
-
Run the web app:
script/frontend
Or to start without opening the browser:
script/frontend --no-open
-
Use:
- Visit http://localhost:3000 (SvelteKit frontend)
- API runs on http://localhost:3001 (Go backend)
- Press the microphone button on the record page
- Say what you consumed (e.g., "I had a latte with organic whole milk and Greek yogurt with blueberries")
- See your nutrition summary with client-side unit conversion (grams β ounces)
First, in two separate terminal windows, run the backend and frontend:
script/server(backend)script/frontend(frontend)
Now, in a third terminal, run:
npm run mobile:sync
npm run iosGET /api/v1/healthβ Health check with server statusPOST /api/v1/consumptionβ Multipart form withaudiofield (webm/opus/mp3/wav)GET /api/v1/consumptionsβ View stored consumptions with optional date range filtering. Supports tier-based limits: Free users can access up to 7 days, Pro users up to 365 days (development mode only)GET /api/v1/docsβ Interactive API documentation via Swagger UI (development mode only)GET /api/v1/openapi.yamlβ OpenAPI 3.0.3 specification (development mode only)
GET /versionβ Version and build information including commit SHA, build time, and git tag
Note: The embedded static frontend has been removed. The API now runs on port 3001 by default, and the SvelteKit frontend runs on port 3000.
- Test:
script/test - Lint:
script/lint - Build:
script/build(orscript/build --single-targetfor faster iteration) - Dev Server:
PORT=3001 script/server(usescript/server --productionfor production mode) - API Docs: Visit http://localhost:3001/api/v1/docs when running in development mode
- Dev Server:
script/frontend(runs on port 3000 and opens browser) - Dev Server (no browser):
script/frontend --no-open - Manual Dev:
cd apps/web && npm run dev(alternative manual approach) - Build:
cd apps/web && npm run build - Type Check:
cd apps/web && npm run check - Generate API Types:
cd apps/web && npm run generate:api(regenerates types from OpenAPI spec)
The API follows OpenAPI 3.0.3 specification:
- Specification:
/api/v1/openapi.yaml - Documentation:
/api/v1/docs(Swagger UI, development only) - Generate Types:
script/generate-types(requires oapi-codegen via the go toolchain - vendored in this project) - Frontend API Types: Generated automatically by
openapi-typescriptfrom the OpenAPI spec
- Reset:
script/db resetβ Drop all tables and start fresh - Seed: Handled by Supabase CLI during local development setup
- Dump:
script/db dumpβ View database contents in human-readable format
The application supports automated deployment with built-in version tracking:
- Deploy Frontend:
script/deploy frontendβ Deploy SvelteKit app to Cloudflare Workers with git metadata - Deploy Backend:
script/deploy backendβ Deploy Go API to Railway - Deploy Both:
script/deployβ Deploy both frontend and backend
Version Tracking:
- Each deployment automatically includes commit SHA, build time, and git tag
- Frontend deployments inject git metadata as environment variables during build
- Access version info at
/versionendpoint (both JSON API and UI page) - Git metadata is extracted automatically during deployment process
Requirements:
- Frontend:
CLOUDFLARE_ACCOUNT_IDandCLOUDFLARE_API_TOKENenvironment variables - Backend:
RAILWAY_TOKENenvironment variable
- Supabase/PostgreSQL database with CLI-managed migrations and seeding for consumption storage
- OpenAI gpt-4o-mini-transcribe for speech-to-text
- OpenAI gpt-4o-mini for consumption parsing with complete nutrition data
- Audio uploads are streamed to temporary files to avoid memory spikes
- JWT authentication via Supabase Auth for user management
- CORS middleware for cross-origin requests from frontend
- SvelteKit with TypeScript
- DaisyUI with custom "noot" theme for styling
- openapi-fetch for type-safe API calls
- Client-side unit conversion (grams β ounces) with localStorage persistence
- MediaRecorder API for audio capture (webm/opus format preferred)
- Responsive design with mobile-friendly recording interface
- Frontend: SvelteKit (http://localhost:3000)
- Backend: Go API server (http://localhost:3001)
- Database: Supabase/PostgreSQL with automatic migrations
- Audio Processing: OpenAI Whisper via API

