6.7 KiB
6.7 KiB
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 recipePOST /api/recipes— Create recipePUT /api/recipes/:id— Update recipeDELETE /api/recipes/:id— Delete recipeGET /api/tags— List tagsPOST /api/recipes/:id/tags— Add tag to recipe
Response format:
{
"success": true,
"data": { ... },
"error": null
}
3. Data Model
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
# 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
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
- Recipe scraping: Puppeteer vs Playwright vs Cheerio?
- Image storage: Filesystem vs base64 in DB vs S3?
- Serving size scaling: Fractional math library needed?
- Export format: JSON, CSV, or standard (Recipe Schema.org)?
Agents: Add Architecture Decision Records (ADRs) in docs/ as you make significant choices.