# Recipe Manager — Architecture **Last Updated:** 2026-03-23 **Status:** Initial Design --- ## Tech Stack ### Backend - **Runtime:** Node.js 22+ - **Language:** TypeScript 5+ - **Framework:** Express.js (REST API) - **Database:** SQLite 3 (better-sqlite3 for sync operations) - **Validation:** Zod (schema validation) - **Testing:** Vitest (unit), Supertest (integration) ### Frontend - **Framework:** React 18+ - **Build Tool:** Vite - **Routing:** React Router 6 - **State:** React Context + hooks (Zustand if needed) - **Forms:** React Hook Form - **Styling:** Tailwind CSS - **Testing:** Vitest + Testing Library ### DevOps - **Containerization:** Docker + Docker Compose - **Reverse Proxy:** Caddy (auto-HTTPS, simple config) - **Process Manager:** PM2 (for non-Docker deployments) - **Backup:** Litestream (continuous SQLite replication) --- ## Architecture Patterns ### 1. Layered Architecture ``` ┌─────────────────────────────────────┐ │ Frontend (React) │ │ Components → Hooks → API Client │ └──────────────┬──────────────────────┘ │ HTTP/JSON ┌──────────────┴──────────────────────┐ │ Backend (Express) │ │ Routes → Services → Repositories │ └──────────────┬──────────────────────┘ │ SQL ┌──────────────┴──────────────────────┐ │ Database (SQLite) │ │ recipes, tags, collections │ └─────────────────────────────────────┘ ``` ### 2. API Design **RESTful endpoints:** - `GET /api/recipes` — List recipes (with filters, pagination) - `GET /api/recipes/:id` — Get single recipe - `POST /api/recipes` — Create recipe - `PUT /api/recipes/:id` — Update recipe - `DELETE /api/recipes/:id` — Delete recipe - `GET /api/tags` — List tags - `POST /api/recipes/:id/tags` — Add tag to recipe **Response format:** ```json { "success": true, "data": { ... }, "error": null } ``` ### 3. Data Model ```sql CREATE TABLE recipes ( id INTEGER PRIMARY KEY AUTOINCREMENT, title TEXT NOT NULL, description TEXT, ingredients TEXT NOT NULL, -- JSON array instructions TEXT NOT NULL, -- JSON array of steps source_url TEXT, notes TEXT, servings INTEGER, prep_time_minutes INTEGER, cook_time_minutes INTEGER, created_at INTEGER NOT NULL, -- Unix timestamp updated_at INTEGER NOT NULL, last_cooked_at INTEGER ); CREATE TABLE tags ( id INTEGER PRIMARY KEY AUTOINCREMENT, name TEXT UNIQUE NOT NULL, color TEXT -- Hex color for UI ); CREATE TABLE recipe_tags ( recipe_id INTEGER NOT NULL, tag_id INTEGER NOT NULL, PRIMARY KEY (recipe_id, tag_id), FOREIGN KEY (recipe_id) REFERENCES recipes(id) ON DELETE CASCADE, FOREIGN KEY (tag_id) REFERENCES tags(id) ON DELETE CASCADE ); CREATE INDEX idx_recipes_title ON recipes(title); CREATE INDEX idx_recipes_created_at ON recipes(created_at DESC); CREATE INDEX idx_recipe_tags_recipe ON recipe_tags(recipe_id); CREATE INDEX idx_recipe_tags_tag ON recipe_tags(tag_id); ``` ### 4. Frontend Structure ``` src/ ├── components/ │ ├── RecipeCard.tsx │ ├── RecipeForm.tsx │ ├── RecipeDetail.tsx │ ├── CookMode.tsx │ └── SearchBar.tsx ├── hooks/ │ ├── useRecipes.ts │ ├── useRecipe.ts │ └── useTags.ts ├── services/ │ └── api.ts -- API client ├── types/ │ └── recipe.ts -- TypeScript interfaces ├── pages/ │ ├── HomePage.tsx │ ├── RecipeListPage.tsx │ ├── RecipeDetailPage.tsx │ └── CookModePage.tsx ├── App.tsx └── main.tsx ``` --- ## Key Decisions ### Why better-sqlite3 over node-sqlite3? - Synchronous API (simpler error handling) - Faster performance (native bindings) - Better TypeScript support ### Why Tailwind CSS? - Rapid prototyping - Agent-friendly (well-documented utility classes) - No CSS-in-JS overhead ### Why Vite over Create-React-App? - Faster dev server (ESM-native) - Simpler config - Better TypeScript support ### Why Docker Compose? - Consistent dev/prod environment - Easy to add services (reverse proxy, backup) - Paul already uses Docker for fintrove --- ## Development Workflow ### 1. Agent Iteration Loop ```bash # Agent reads PROJECT.md + ARCHITECTURE.md # Agent plans next feature # Agent implements + tests # Agent commits with conventional commit message # Agent updates docs/ADR-xxx.md # Agent reports completion ``` ### 2. Testing Strategy - **Unit tests:** Core business logic (recipe parsing, scaling calculations) - **Integration tests:** API endpoints - **E2E tests:** Critical user flows (create recipe → cook mode) ### 3. Commit Conventions ``` feat: add recipe scaling fix: correct ingredient parsing docs: update architecture decisions test: add cook mode E2E tests chore: bump dependencies ``` --- ## Deployment ### Docker Compose Stack ```yaml services: recipe-manager: build: . ports: - "3000:3000" volumes: - ./data:/app/data # SQLite database environment: NODE_ENV: production DATABASE_PATH: /app/data/recipes.db caddy: image: caddy:2 ports: - "80:80" - "443:443" volumes: - ./Caddyfile:/etc/caddy/Caddyfile - caddy_data:/data depends_on: - recipe-manager volumes: caddy_data: ``` ### Backup Strategy - **Litestream:** Continuous replication to S3/B2 - **Manual exports:** Weekly JSON dumps for portability - **Git LFS:** Track database snapshots (optional) --- ## Security Considerations ### MVP (Single Household) - No authentication (behind home network or VPN) - HTTPS via Caddy (Let's Encrypt) - Input validation (prevent SQL injection via Zod) ### Future (Multi-User) - JWT-based auth - Per-user recipe collections - Invite-only signup --- ## Performance Goals - **Page load:** <1s on 3G connection - **Recipe search:** <100ms for 1000 recipes - **Cook mode:** Wake lock prevents screen sleep - **Offline support:** Service worker (PWA) in v1 --- ## Open Questions for Agents 1. **Recipe scraping:** Puppeteer vs Playwright vs Cheerio? 2. **Image storage:** Filesystem vs base64 in DB vs S3? 3. **Serving size scaling:** Fractional math library needed? 4. **Export format:** JSON, CSV, or standard (Recipe Schema.org)? --- _Agents: Add Architecture Decision Records (ADRs) in `docs/` as you make significant choices._