Aller au contenu

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)
Calyx: snap your meal, we'll handle the rest.

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.

Features · Architecture


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

server/ — NestJS API on Bun (CQRS, hexagonal architecture)
web/ — TanStack Start web front end
native/ — Expo SDK 58 mobile app + expo-router + Uniwind
db/ — Prisma 7 multi-file schema, migrations
auth/ — Better Auth configuration
contract/ — OpenAPI contract generated from the server
api-client/ — TypeScript client generated by orval
ui/ — Shared shadcn/ui components
config/ — Shared TypeScript configuration
docker-compose.yml — Postgres and Redis for development

🧠 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 the CommandBus or QueryBus. 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_ANALYZER lets 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

  1. Monorepo

    • Nx
    • Bun (workspaces)
  2. Mobile

    • Expo SDK 58
    • expo-router
    • Uniwind
    • MMKV
    • jest-expo
  3. Web

    • TanStack Start
    • React
    • Tailwind CSS
    • shadcn/ui
  4. API

    • NestJS
    • Bun
    • CQRS (CommandBus / QueryBus)
    • Zod (nestjs-zod)
    • OpenAPI
  5. Auth

    • Better Auth
    • Global AuthGuard
  6. Database

    • PostgreSQL
    • Prisma 7 (multi-file schema)
  7. Job queues

    • BullMQ
    • Redis
  8. Storage

    • Supabase Storage
    • Signed URLs
  9. API client

    • orval
    • Shared OpenAPI contract
  10. Quality

    • Oxlint + Oxfmt
    • Jest
    • e2e tests on Bun
    • GitHub Actions
  11. 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

Next project

Neovim Configuration

A fast, TypeScript/JavaScript-focused Neovim setup built on NvChad v2.5, with automatic macOS theme syncing, an oxc-powered lint/format toolchain, and Zellij pane integration.