AI-powered food logging for Google Health. Take a photo of your meal, let Claude analyze the nutrition, and log it directly to Google Health.
- Photo capture — Take a photo of your food with your phone camera
- AI analysis — Claude analyzes the image and estimates nutritional information
- Review & edit — Confirm or adjust the nutrition data
- Log to Google Health — Post directly to your Google Health food log
Single-user application with email allowlist.
- Next.js 16+ (App Router, TypeScript)
- Tailwind CSS + shadcn/ui for styling
- iron-session for encrypted cookie-based sessions
- Google OAuth 2.0 for authentication and Google Health API access
- Google Health REST API for food logging (single shared OAuth client)
- Anthropic Claude API for nutrition analysis (tool_use)
- PostgreSQL via Railway (Drizzle ORM)
- Railway for deployment
| Environment | Branch | URL | Health API |
|---|---|---|---|
| Production | release |
food.lucaswall.me |
Live |
| Staging | main |
food-test.lucaswall.me |
Dry-run (HEALTH_DRY_RUN=true) |
Branch strategy:
main— development branch, auto-deploys to stagingrelease— stable branch, auto-deploys to production- Feature branches → PR to
main→ merge to staging → mergemaintorelease
Each environment has its own Railway Postgres, environment variables, and domain.
Promotion flow: Merge main → release to deploy to production.
- Railway account
- Railway CLI installed and authenticated (
railway login) - GitHub repository pushed to origin
- Go to railway.com/new
- Choose "Deploy from GitHub Repo"
- Select the
food-scannerrepository - Railway creates the project and auto-deploys
Do not use railway init — that's for manual deploys, not GitHub-linked projects.
From the project directory:
railway linkSelect the food-scanner project and environment when prompted. This stores the link in ~/.railway/config.json (global, not in the repo).
Follow the OAuth Setup section below to create Google OAuth credentials before setting environment variables.
railway variable set \
SESSION_SECRET="$(openssl rand -base64 32)" \
HEALTH_TOKEN_ENCRYPTION_KEY="$(openssl rand -base64 32)" \
ALLOWED_EMAILS=wall.lucas@gmail.com \
APP_URL=https://food.lucaswall.me \
GOOGLE_CLIENT_ID=your-google-client-id \
GOOGLE_CLIENT_SECRET=your-google-client-secret \
ANTHROPIC_API_KEY=your-anthropic-api-key \
LOG_LEVEL=infoFor staging, also set:
railway variable set HEALTH_DRY_RUN=trueNote:
HEALTH_TOKEN_ENCRYPTION_KEYis a dedicated 32-byte random key for encrypting Google Health tokens at rest. Rotating this key invalidates all stored tokens — users must re-link Google Health after rotation.
Set real values for all credentials.
Do not set NODE_ENV — Railway handles this automatically. Setting NODE_ENV=development breaks the Next.js production build.
railway domainThis creates a public URL like https://food-scanner-production-XXXX.up.railway.app.
Once you have your Railway domain, go back to the Google Cloud Console and add the production redirect URI:
- Google:
https://<your-railway-domain>/api/auth/google/callback
curl https://food-scanner-production-XXXX.up.railway.app/api/healthShould return { "success": true, "data": { "status": "ok" }, "timestamp": ... }.
- Open your project in the Railway dashboard
- Click "+ New" on the Project Canvas
- Select "Database" → "PostgreSQL"
- Railway creates a Postgres service with auto-generated credentials
- Click on the food-scanner service in the canvas
- Go to the Variables tab
- Click "+ New Variable"
- Add
DATABASE_URLwith value${{Postgres.DATABASE_URL}}— This uses Railway's reference variable syntax to dynamically resolve the Postgres connection string - Railway will redeploy automatically after saving
After the redeploy completes:
railway logsLook for successful startup with no database connection errors. The app runs migrations automatically on startup — no manual migration step is needed.
Railway builds with Railpack, which auto-detects Next.js:
- Build:
npm run build(runsnext build) - Start:
npm start(runsnext start) - Health check:
GET /api/healthreturns 200
railpack.json replaces Railpack's deprecated NPM_CONFIG_PRODUCTION=false with NPM_CONFIG_INCLUDE=dev, so the build still installs devDependencies without npm's production config warning on every install, build and start.
Watch Paths are set in the Railway service settings (Settings → Build), not in a repo file — Railway's railway.json Config as Code is deprecated. Keep them in sync with the app paths in .github/workflows/ci.yml, plus railpack.json and .node-version.
railway logs # Stream deploy logs
railway logs --build # Stream build logsOr use the Railway MCP from Claude Code to query logs and deployment status.
- Add custom domain in Railway dashboard → Settings → Networking
- Configure DNS (CNAME record pointing to Railway)
- Update OAuth redirect URIs in the Google Cloud Console to use the custom domain
Claude powers the AI food analysis. The model id lives in src/lib/claude.ts.
- Go to console.anthropic.com
- Create an account or sign in
- Navigate to API Keys in the sidebar
- Click Create Key and copy the API key
- Add to Railway:
ANTHROPIC_API_KEY=sk-ant-api03-...
Note: The API key is included in Step 4 (Set Environment Variables) above.
Food Scanner uses a single shared Google OAuth client for both login and Google Health API access.
- Go to Google Cloud Console
- Create a new project (or select existing)
- Navigate to APIs & Services → Credentials
- Click Create Credentials → OAuth 2.0 Client ID
- Select application type: Web application
- Under Authorized redirect URIs, add your environment URLs:
- Production:
https://food.lucaswall.me/api/auth/google/callback - Staging:
https://<staging-railway-domain>/api/auth/google/callback - Local:
http://localhost:3000/api/auth/google/callback
- Production:
- Copy the Client ID and Client Secret
Configured on the GCP OAuth consent screen, not via environment variables. The four scopes requested (see GOOGLE_HEALTH_SCOPES in src/lib/auth.ts) are all under https://www.googleapis.com/auth/:
googlehealth.nutrition.writeonlygooglehealth.profile.readonlygooglehealth.health_metrics_and_measurements.readonlygooglehealth.activity_and_fitness.readonly
IMPORTANT: every googlehealth.* scope is restricted. That requires OAuth verification plus an annual CASA assessment, and the consent screen must be In production — while it is in Testing, Google revokes refresh tokens every 7 days and caps the app at 100 users.
Users connect via the one-click /app/connect-health flow after logging in.
See DEVELOPMENT.md for complete local setup instructions.
The app supports "Add to Home Screen" on mobile devices for a native-like experience.
Features:
- Standalone display mode (no browser chrome)
- Custom app icon on home screen
- Portrait orientation lock
To install:
- Open the app in Safari (iOS) or Chrome (Android)
- Tap the Share button → "Add to Home Screen"
- The app launches in standalone mode
Customizing icons:
Replace the placeholder icons in public/:
icon-192.png— 192x192 PNGicon-512.png— 512x512 PNG
No service worker or offline support — the app requires an internet connection.
| File | Description |
|---|---|
| API.md | External API reference (auth, endpoints, schemas) |
| ROADMAP.md | Feature ideas and specifications |
| CLAUDE.md | Technical reference for Claude Code |
| DEVELOPMENT.md | Local development setup |
| MIGRATIONS.md | Pending production data migrations |
| PLANS.md | Current implementation plan (managed by skills) |
| CHANGELOG.md | Version history |
| README.md | This file — deployment and operations |