Calyx
An AI-powered nutrition app: snap a photo of your meal to estimate calories, macros and micros, get recipe ideas and share your meals in a social feed.
- TypeScript
- NestJS (Bun)
- Expo
- TanStack Start
- Postgres (Prisma)

One photo. Your calories, macros and micros.
A web and mobile app that analyzes a photo of your meal with AI, estimates its nutritional content, suggests matching recipes and lets you share your meals with a community.
About
Tracking what you eat usually means looking up every food in a database, weighing, typing, correcting… and giving up after three days.
Calyx starts from a simple idea:
The best food journal is the one you don't have to fill in.
You take a photo of your meal. A background job queue hands the image to an AI analyzer, which returns an estimate of calories, macros and micronutrients. The result lands in the app, ready to feed your daily summary, your goals and a social feed.
✨ Features
📸 Meal analysis from a photo
A dedicated camera tab, one shot, and the meal goes from "pending" to "ready" without blocking the UI. The analysis runs in the background, and the app reflects its state live.
Capture · built-in camera
🏠 Home & daily tracking
Calorie card, today's goal, weekly calendar and a list of recent meals: everything at a glance.
Home · calories & recent meals
🖼️ Private media
Photos are uploaded to object storage behind a STORAGE port, then read back through time-limited signed URLs. No image is public by default.
🔐 Authentication
Accounts, sessions and routes protected by default with Better Auth: a global AuthGuard, and a @Public() decorator to explicitly open a route.
🍽️ Recipes & community (work in progress)
Recipe suggestions based on analyzed meals, and a social feed to share your plates. The data model is in place; the use cases come next.
🧱 Architecture
🧠 The server: a strict architecture
The most defining part of the project. Every API module follows the same layers, with verifiable dependency rules:
interface→application→domain←infrastructure— controllers only parse input and dispatch to theCommandBusorQueryBus. No business logic.- One handler per command or query — small, testable, named by intent.
- A dependency-free domain — no Nest, Prisma or vendor SDKs. Ports are interfaces with an injection token, and adapters implement them in
infrastructure. - Watertight modules — a module never imports another module's services. Cross-context side effects go through domain events.
- AI behind a port —
NUTRITION_ANALYZERlets a fake analyzer be plugged in for development and tests, then a real model without touching anything else. - An end-to-end contract — zod DTOs produce the OpenAPI spec, committed and checked in CI, which in turn generates the client used by web and mobile.
🛠️ Tech stack
Monorepo
- Nx
- Bun (workspaces)
Mobile
- Expo SDK 58
- expo-router
- Uniwind
- MMKV
- jest-expo
Web
- TanStack Start
- React
- Tailwind CSS
- shadcn/ui
API
- NestJS
- Bun
- CQRS (CommandBus / QueryBus)
- Zod (nestjs-zod)
- OpenAPI
Auth
- Better Auth
- Global AuthGuard
Database
- PostgreSQL
- Prisma 7 (multi-file schema)
Job queues
- BullMQ
- Redis
Storage
- Supabase Storage
- Signed URLs
API client
- orval
- Shared OpenAPI contract
Quality
- Oxlint + Oxfmt
- Jest
- e2e tests on Bun
- GitHub Actions
Config
- Varlock
- Typed .env.schema
💡 What I learned
- Keeping the domain clean. Enforcing ports and adapters from day one costs a bit of ceremony, but it lets you swap the AI, the storage or the ORM without rewriting business logic.
- Treating AI as a slow, fallible service. An analysis isn't a synchronous request: a job queue, explicit states (pending, ready) and retries are needed for a smooth experience.
- Making the contract the source of truth. The server generates the OpenAPI spec, the spec generates the client, and CI fails if one drifts from the other: web and mobile can no longer fall out of sync unnoticed.
- Making web and mobile coexist in a single repo. Sharing auth, the client and configuration between TanStack Start and Expo demands discipline around dependencies and Metro config.
📸 Screenshots
Meal · macros & micros
Week · goal tracking
Profile · API status
🛣️ Roadmap
- Authentication and protected routes
- API health checks (liveness + readiness)
- Prisma schema and migrations
- Meal creation → analysis queue → "ready" status
- Photo uploads and signed URLs
- Mobile home screen (calories, recent meals)
- OpenAPI contract and generated client, with drift check in CI
- Real nutrition analysis model (replacing the fake analyzer)
- Validate the storage adapter against a real bucket
- Recipe suggestions
- Community feed and meal sharing
- Web app: working screens