Nutrilens 开源饮食记录应用:AI 识别服务与主服务隔离部署
Wolfi-OwO/nutrilens
Nutrilens 将应用拆分为 apps/api(Node.js/TypeScript、Express、PostgreSQL,负责用户、认证、饮食计划与餐食记录)和 apps/ai-server(Python/FastAPI、ONNX Runtime,仅做食物照片推理、不存任何用户数据)两个可独立部署的服务。
Why it is built this way
Nutrilens is deliberately split into two independently deployable services (see ADR-0001):
apps/api— the primary application server (Node.js/TypeScript, Express, PostgreSQL). Owns users, authentication, diet plans, meal logs, and all persistent data.apps/ai-server— a standalone AI-detection service (Python/FastAPI, ONNX Runtime). Owns nothing but inference: it receives a food photo, returns a structured prediction, and holds no user data of its own — no database, no persisted images, no logs containing image content (seeapps/ai-server/API.md).
The API server is the only caller of the AI server, over an internal network
segment the AI server cannot be reached from publicly. A vulnerability or a
compromised dependency in the inference stack (a much larger, faster-moving
set of ML packages) is contained to a service that has no database
credentials, no user PII, and no session state. This mirrors the separation
used across this account's projects for stack isolation (see
cli-image-upscaler for
the same principle applied to a single binary's AI extras).
A food photo goes to apps/ai-server — a real food-recognition model (see
ADR-0002) behind a
timeout/retry/circuit-breaker policy (see
ADR-0003), so an
AI-server outage degrades to manual meal logging rather than blocking the
user.
apps/api (serving the built apps/frontend from the same container) and
apps/ai-server run behind Caddy on a Contabo VPS, via
docker-compose.prod.yml at /opt/nutrilens. That replaced the original
Azure PaaS hosting in the 2026-09 migration; only the image push to the
shared Azure Container Registry (globalcr01) survives from that pipeline
(see .github/workflows/release.yml).
ui-prototype/ is retired — a static, hardcoded-data
walkthrough kept only as a historical reference for the original design
pass; every page it mocked is now a real, backend-wired page under
apps/frontend. Still ahead: observability (M7), a hardening pass (M8), and
an admin dashboard (M9) — none of it required for the app to work, all of it
scoped as post-v0.0.1 follow-up. See the issue tracker and
milestones for sequencing, and
organizational/ for use cases, activity diagrams,
requirements, and ADRs.
Tech stack
| Component | Stack |
|---|---|
| Frontend | React, Vite, TypeScript, Tailwind CSS, React Router, TanStack Query |
| API server | Node.js, TypeScript, Express, PostgreSQL, zod |
| AI server | Python, FastAPI, ONNX Runtime (food-recognition model) |
| Infra | Docker per service, Contabo VPS (Caddy + docker-compose.prod.yml), shared ACR for image pushes |
apps/api's HTTP API is RESTful at Richardson Maturity Model level 3 —
correct verbs/status codes, _links (HATEOAS) on every JSON representation,
and a GET /api hypermedia entry point. See the full
API reference.
Getting started
cp .env.example .env docker compose up
Brings up Postgres, apps/api (migrated and listening on :8080), and
apps/ai-server (internal-only, no published port — reachable from apps/api
as http://ai-server:8000) together. Each service also has its own
docker-compose.yml for developing it in isolation
(apps/api/docker-compose.yml, apps/ai-server/docker-compose.yml).
apps/frontend isn't containerized yet — run it separately:
cp apps/frontend/.env.example apps/frontend/.env npm run dev --workspace=@nutrilens/frontend
Project structure
organizational/ Use cases, activity diagrams, requirements, ADRs, deploy docs
ui-prototype/ Retired — historical, hardcoded-data prototype, no backend calls
apps/frontend/ Production frontend (React/Vite/TypeScript)
apps/api/ Main application server (Node.js/TypeScript)
apps/ai-server/ Isolated AI-detection service (Python/FastAPI)
e2e/ End-to-end + accessibility tests (Playwright), run against
the real docker-compose stack — not one app on its own
Contributing
See CONTRIBUTING.md. main is protected — all changes
land through a pull request from a branch created off an issue, gated on CI
(lint, typecheck, tests, coverage thresholds), an end-to-end + accessibility
suite against the real running stack, CodeQL, Trivy, and secret scanning all
passing.
Security
See SECURITY.md for how to report a vulnerability.
License
来源:GitHub · github.com