Back to Blog
FastAPIDockerExpoReactGemini AIPWA

Building a Multi-Platform Calorie Tracker: FastAPI + Docker + Expo

January 15, 20262 min read

The Problem with Existing Apps

Most calorie trackers are either too complex or lack a proper Turkish food database. I also wanted something that runs on both phone and computer, deploys with a single Docker command, and optionally works offline. That became KiloTakip (Weight Tracker).

Architecture: "One Brain, Many Clients"

The core design decision: a single FastAPI backend serving multiple clients.

FastAPI (Python) ← single source of truth
      ↓
  ┌───┼───┐
 Web  PWA  Mobile
(React)(nginx)(Expo RN)

Business logic lives once in Python: calorie calculations, BMR/TDEE formulas (Mifflin-St Jeor), data validation. Tested once, used everywhere.

Gemini AI Food Recognition

The standout feature: take a photo of your meal and ask "how many calories?"

@app.post("/api/photo-recognize")
async def recognize_food(file: UploadFile):
    image_data = base64.b64encode(await file.read()).decode()
    response = genai.GenerativeModel('gemini-1.5-flash').generate_content([
        "What food is this? Return calories, protein, carbs, fat as JSON.",
        {"inline_data": {"mime_type": "image/jpeg", "data": image_data}}
    ])
    return parse_gemini_response(response.text)

Gemini's multimodal capabilities shine here — it recognizes Turkish dish names and estimates reasonable portion sizes.

607 Turkish Foods Database

The biggest gap in existing apps: Turkish cuisine. I built a 607-item database covering home cooking, street food, and restaurant meals. A custom normalizer handles Turkish character variations (ş/s, ğ/g) for fuzzy search.

Docker: One-Command Startup

services:
  backend:
    build: ./backend
    ports: ["8000:8000"]
    volumes: ["kilo-data:/app/data"]
  frontend:
    build: ./frontend
    ports: ["3000:80"]

docker compose up -d --build — and it's running. Open in browser and "Add to Home Screen" for the PWA version.

Offline-First Mobile with Expo

The Expo app is fully independent — SQLite on-device, Gemini API key embedded, no backend required. A built APK (72MB) installs directly on Android.

Key Learnings

  1. Docker volumes: Named volumes are essential for SQLite persistence outside containers.
  2. Expo + local API: Must use the machine's IP address, not localhost, when testing on a real device.
  3. PWA manifest: "display": "standalone" and a correct start_url are critical for proper "Add to Home Screen" behavior.
  4. Gemini rate limits: Free tier is 15 requests/minute — show users a clear error when throttled.

View the project: GitHub