- TypeScript 61.2%
- Python 25.4%
- CSS 9.4%
- Shell 3.6%
- Dockerfile 0.2%
- Other 0.2%
| .run | ||
| .venv | ||
| backend | ||
| data | ||
| frontend | ||
| scripts | ||
| systemd | ||
| .env.example | ||
| .gitignore | ||
| boot-backend.sh | ||
| boot-frontend.sh | ||
| boot-start.sh | ||
| DESIGN_DECISIONS.md | ||
| docker-compose.yml | ||
| install-autostart.sh | ||
| README.md | ||
| start-local.sh | ||
Closet App
A fullstack closet management web app with AI-assisted item tagging, outfit building, capsule management, favorites, calendar-based outfit history, and a daily outfit planner.
Stack
- Frontend: Next.js 15 + TypeScript, Fraunces (display) + Manrope (body) via
next/font - Backend: FastAPI + SQLite
- AI: OpenAI-compatible local or cloud vision models (llama.cpp, Ollama, OpenAI, Groq, or custom)
- Deployment: local
start-local.shor systemd services (closet-backend.service,closet-app.service)
Running Locally
./start-local.sh
When run from an interactive terminal, this starts the FastAPI backend (port 8000) plus the Next.js frontend in development mode (port 5050), both bound to 0.0.0.0.
This avoids a full production rebuild on every local restart, which makes startup faster and more reliable while you are editing the app.
Note: if you run ./start-local.sh from a terminal, the app stops when that terminal/session exits. Use the systemd setup below for reboot-safe background startup.
To force the production frontend path locally:
APP_START_MODE=production ./start-local.sh
To force a fresh production rebuild before startup:
APP_START_MODE=production FORCE_FRONTEND_BUILD=1 ./start-local.sh
- Frontend:
http://localhost:5050 - Backend API:
http://localhost:8000 - API docs:
http://localhost:8000/docs
Auto-start on boot (systemd)
From the project root:
- Create and edit your local service files from the templates:
cp systemd/closet-backend.service.example systemd/closet-backend.service
cp systemd/closet-app.service.example systemd/closet-app.service
# edit both files and set:
# - User
# - WorkingDirectory / ExecStart / HOME / PATH
# - CLOSET_API_WRITE_KEY
- Install and enable it:
sudo ./install-autostart.sh
This installs and enables both services so the app starts on reboot:
closet-backend.servicefor FastAPIcloset-app.servicefor the Next.js frontend
The frontend unit depends on the backend unit, but they can be restarted independently.
Production note:
boot-frontend.shruns a productionnext buildbefore launching the standalone server.- During that rebuild window after restart, reverse proxies may briefly return
502until the frontend is ready.
Useful commands:
sudo systemctl status closet-backend closet-app
sudo systemctl restart closet-backend
sudo systemctl restart closet-app
journalctl -u closet-backend -u closet-app -f
For reverse-proxied deployments, ensure the service environment includes:
NEXT_PUBLIC_API_BASE_URL=
This keeps API/image calls on same-origin /api/... through the proxy.
Reverse proxy runtime behavior (important)
For deployments behind domain auth/proxy (for example https://closet.hybridcloudhq.com):
- Frontend write/read API calls are expected to use same-origin
/api/*. - Frontend static assets use Next.js's default
/_next/static/*paths. Verify this path through the live proxy after any proxy or Next.js configuration change. - Uploaded images continue to resolve through
/api/files/items/*.
Reverse proxy note for images
Uploaded item photos are served from /api/files/items/....
If you proxy the app with Caddy/Nginx/Traefik, make sure your backend route includes /api/files/* (or routes all /api/*), otherwise item images will not load.
One-time image URL cleanup
Run this if you want to normalize legacy item image URLs in the database to the current canonical format (/api/image?file=...).
./scripts/normalize-image-urls.sh
The script creates a timestamped backup in data/closet.db.bak.YYYYMMDD-HHMMSS before updating rows.
Database backup and restore
The Profile page now includes a Database section where you can:
- download a full SQLite backup of the current closet database
- restore the app from a previously saved
.db/.sqlitebackup file
Restore uploads are validated before replacement, and the app automatically creates a fresh pre-restore backup of the current database file before swapping it out.
If you prefer using the API directly:
GET /api/profile/database/backup
POST /api/profile/database/restore
Shared closet options
Closet categories, fit/style/fabric options, and color swatches are centralized in:
frontend/lib/closet-options.ts
Update that file when you want to add or rename selectable closet metadata across the add-item and edit-item flows.
Optional write API key protection
You can require an API key for all write routes (POST, PUT, PATCH, DELETE) under /api/*.
- Set a key for the backend service:
CLOSET_API_WRITE_KEY=your-long-random-key
- Send the same value in request header
X-API-Keyfor write calls.
If you run behind Nginx Proxy Manager, set this as a Custom Request Header so browser clients do not need to embed a public key:
X-API-Key: your-long-random-key
Read-only endpoints (GET) are not affected.
Remote access
The frontend is accessible from other machines on the LAN at http://<host-ip>:5050. The API base URL is set in frontend/.env.local:
NEXT_PUBLIC_API_BASE_URL=http://<host-ip>:8000
For reverse-proxied deployments (for example, https://closet.hybridcloudhq.com), set:
NEXT_PUBLIC_API_BASE_URL=
This makes frontend requests use same-origin /api/... paths through the proxy.
The backend runs with allow_origins=["*"] — suitable for homelab use. Backend CORS can be tightened via the CORS_ORIGINS environment variable.
What's Built
Closet Management
- Add, edit, and delete clothing items with rich metadata (category, colors, fabric, fit, cut, neck, sleeve, waist, length, rise, capsule membership, love factor, price, shopping links)
- Photo upload with camera capture support
- Capsule groupings — create subsets of your closet and filter the view by capsule
- Item thumbnails in closet grid view
- Tracked categories: Layers, Shirts, Pants, Dresses, Rhompers, Skirts, Shorts, Shoes, Accessories
AI Item Suggestions
- Upload a photo when adding an item, tap ✦ Suggest with AI and the model fills in name, category, colors, fabric, fit, cut, sleeve, neck, and style
- Works with any OpenAI-compatible vision endpoint
- Supports Qwen3 / llama.cpp models that return reasoning in
reasoning_contentinstead ofcontent - Configurable AI provider in Profile settings: Local (llama.cpp), Ollama, OpenAI, Groq, or Custom
Outfit Engine
- Random outfit — color-harmony-scored generator:
- Shirt + bottom (pants/skirt/shorts), or dress, or rhomper + optional shirt
- One shoe; optional accessory
- Layer added automatically when temperature is below 65°F
- Custom builder — slot-by-slot picker with dense visual tiles, explicit click-to-expand/click-again-to-collapse behavior, and no default preselected slot
- Edit outfit — modify items on any saved outfit
- Save or Save & wear today from any generated or custom outfit
- Favorites — persist outfit favorites with quick access under the Outfits Favorites tab
Daily Outfit Planner
- Home page shows today's outfit, or a random suggestion if none is set
- Wear this today — saves and locks the suggestion as today's outfit
- Change outfit — clears today's outfit and navigates to the outfits page
- Recent outfits section on home shows the last 5 worn outfits (3-column on tablet, 2-column on mobile)
Outfit Planning & History
- Create tab feed — defaults to last 7 days (including today)
- Plan outfit — assign any saved outfit to a future date with visual item thumbnails on each selectable outfit card
- Past outfits — calendar view with clickable dates, day markers for logged outfits, and visual outfit cards for the selected day
- Saved outfits list with Wear today, Edit, Favorite, and Remove actions
Home and Navigation
- Home
Closet sectionsquick links route to Outfits sub-tabs:Favorites->/outfits?tab=favoritesRecent->/outfits?tab=create
Visual Design
- Full OKLCH color palette: Chantilly Lace base, Oregano olive accent, Hale Navy text, Topsail teal, Tender Pink, Kentucky Haze
- Fraunces serif display font + Manrope body font
- Responsive layout: 3-breakpoint system (≥900px desktop, 640–900px tablet, ≤640px mobile)
- Bottom navigation bar, floating pill controls, sheet overlays
- Denser closet item cards (desktop-targeted 5-up layout) and compact builder/selector cards to reduce unused whitespace
Other
- Weather-aware outfit suggestions (Open-Meteo API, no key required)
- Profile settings: display name, avatar upload, location, weather preferences
- Shopping needs list with priority ranking
- Analytics page (stub for future expansion)
API Surface
GET /api/health
GET /api/dashboard
GET /api/items
POST /api/items
GET /api/items/{id}
PUT /api/items/{id}
DELETE /api/items/{id}
POST /api/uploads/item-photo
GET /api/capsules
POST /api/capsules
PUT /api/capsules/{name}
DELETE /api/capsules/{name}
GET /api/outfits
POST /api/outfits
GET /api/outfits/random?temperature=72
GET /api/outfits/today
PUT /api/outfits/{id}
PUT /api/outfits/{id}/favorite
DELETE /api/outfits/{id}
POST /api/outfits/{id}/wear-today
POST /api/outfits/{id}/wear-date
DELETE /api/outfits/today
GET /api/ai/config
PUT /api/ai/config
POST /api/ai/suggest-item
GET /api/profile
POST /api/profile
POST /api/profile/change-password
POST /api/profile/logout
GET /api/profile/database/backup
POST /api/profile/database/restore
GET /api/location/reverse
Project Layout
closet-app/
├── backend/
│ ├── main.py # FastAPI app — all routes, DB schema, outfit engine
│ ├── requirements.txt
│ └── uploads/
│ └── items/ # Uploaded item photos
├── frontend/
│ ├── app/
│ │ ├── closet/ # Closet grid + item detail + add-item sheet
│ │ ├── outfits/ # Outfit builder, planner, history
│ │ ├── analytics/ # Wear stats
│ │ ├── shopping/ # Shopping needs
│ │ ├── profile/ # Profile, AI config, preferences
│ │ ├── globals.css # Design system tokens and shared classes
│ │ └── page.tsx # Home dashboard
│ ├── components/
│ │ ├── icon.tsx
│ │ └── profile-pill.tsx
│ └── lib/
│ ├── api.ts # All API fetch functions and types
│ └── fetch-json.ts # Timeout-aware external fetch helper
├── data/
│ └── closet.db # SQLite database (gitignored)
├── boot-backend.sh # Production backend entrypoint for systemd
├── boot-frontend.sh # Production frontend entrypoint for systemd
├── start-local.sh # One-command local startup
├── docker-compose.yml
└── DESIGN_DECISIONS.md
Mobile-Ready Direction
The frontend uses a mobile-style layout (max-width 980px, bottom navigation) and a clean API boundary, making it a direct stepping stone to a React Native client or PWA.
Potential Next Features
- Wear logging per item (track how often each piece is actually worn)
- Cost-per-wear analytics
- Shopping recommendations based on closet gaps
- Multi-closet / multi-user support
- PWA with offline support
- Native mobile client reusing the same backend