|
I used to spend too long deciding what to wear, even when my closet was full. That frustration made the problem feel very clear to me: it was not about having fewer clothes. It was about having better organization, better visibility, and better guidance when making outfit decisions. So I built a fashion web app that helps users organize their wardrobe, get outfit suggestions, evaluate shopping decisions, and improve recommendations over time using feedback. In this article, I’ll walk through what the app does, how I built it, the decisions I made along the way, and the challenges that shaped the final result.
What the App DoesAt a high level, the app combines six core capabilities:
Users can upload clothing items, explore suggested outfits, and mark recommendations as helpful or not helpful. They can also rate outfits and track whether items are worn, kept, or discarded. That feedback becomes structured data for improving future recommendation quality. Why I Built ItI wanted to create something that felt personal and actually useful. A lot of fashion apps look polished, but they do not always help with everyday decisions. My goal was to build something that could make wardrobe management easier and outfit selection less overwhelming. The app needed to do three things well:
That feedback loop mattered to me because it makes the app feel more alive instead of static. Tech StackHere are the tools I used to built the app:
This ended up giving me a pretty modular setup, which helped a lot as features started increasing: fast frontend iteration, clean API boundaries, and room to evolve recommendations separately from UI. Product Walkthrough (What Users See)1. Onboarding and Account SetupTo start using the app, a user needs to register, verify their email, and complete some profile basics. ![]() Each account is isolated, so wardrobe history and recommendations stay user-specific. In this onboarding screen above, you can see account creation, email verification, and profile fields for body shape, height, weight, and style preferences. 2. Wardrobe UploadUsers can upload clothing images . ![]() Image analysis labels each item and makes it searchable for recommendations. The wardrobe upload form shows image analysis results with category, dominant color, secondary color, and pattern details listed. 3. Outfit RecommendationsUsers can request recommendations, then rate outputs. ![]() Above you can see the outfit recommendation dashboard that shows ranked outfit cards with feedback and rating actions. Recommendations are ranked by a weighted scoring model. 4. Shopping and Discard AssistantsThe app evaluates new items against existing wardrobe data and flags low-value wardrobe items that may be worth removing. ![]() You can see the recommendation scores, written reasons (not just a binary decision), and styling guidance for each item above. It also features a "how to style it" incase the user still wants to keep the item. How I Built It1. Frontend Setup (React + Vite)I used React + Vite because I wanted fast iteration and a clean component structure. The frontend is split into feature areas like onboarding, wardrobe management, outfits, shopping, and discarded-item suggestions. I also keep API calls in a service layer so the UI components stay focused on rendering and interaction. The snippet below is a simplified example of the API service pattern used in the app. It is not meant to be copy-pasted as-is, but it shows the same structure the frontend uses when talking to the backend. Example API client pattern: Here's what's happening in that snippet:
This pattern kept the frontend simple and reusable as the number of API calls grew. 2. Backend Architecture with FastAPIThe backend is organized around clear route groups:
One of the most important design choices was enforcing ownership checks on user-scoped resources. That prevented one user from accessing another user’s wardrobe or feedback data. The backend snippet below is another simplified example from the app’s route layer. It shows the request validation and orchestration logic, while the actual scoring work stays in the recommendation service. Here's how to read that code:
That separation helped keep the API layer readable while the recommendation logic stayed isolated in its own service. 3. Recommendation LogicI intentionally started with deterministic rules before introducing heavy ML. That made behavior easier to debug and explain. The outfit recommender scores combinations using weighted signals: $$\text{ outfit score} = 0.4 \cdot \text{ color harmony} + 0.4 \cdot \text{ body-shape fit} + 0.2 \cdot \text{ undertone fit}$$ The snippet below is a simplified example from the recommendation engine. It shows how the app combines multiple signals into a single score: The logic behind this approach is straightforward:
I used a similar structure for discard recommendations and shopping suggestions, but with different factors and thresholds. 4. Authentication and Secure Multi-user DesignSecurity was one of the most important parts of this build. I implemented:
The snippet below is a simplified example of the refresh-token lifecycle used in the app. It shows the important control points rather than every helper function: What this code is doing:
This design made multi-device sessions safer and gave me server-side control over logout behavior. 5. Background Jobs for Long-running OperationsImage analysis can be expensive, especially when the app needs to classify clothing, analyze colors, and estimate body-shape-related signals. To keep the request path responsive, I added Celery + Redis support for background tasks. That gave the app two modes:
That tradeoff mattered because it let me keep the developer experience simple without blocking the app during more expensive work. 6. Data Model and Feedback CaptureA recommendation system only improves if it captures the right signals. So I added dedicated feedback tables for:
Here is the shape of one of those models: How to read this model:
This part of the system gives the app a real learning foundation, even though the feedback-to-model-update loop is still a future improvement. Challenges I FacedThis was the section that taught me the most. 1. Image-heavy endpoints were slower than I wantedThe analyze and wardrobe upload flows were doing a lot of work at once: image validation, classification, color extraction, storage, and database writes. At first, that made the request flow feel heavier than it should have. What I changed:
The practical effect was that heavy image requests stopped competing with each other so aggressively. Instead of letting many expensive tasks pile up inside the same request cycle, I limited the active work and pushed slower operations into the queue when needed. Why this fixed it:
In other words, I didn't just “optimize” the endpoint in theory. I changed the execution model so expensive analysis could not block every other request behind it. 2. JWT sessions needed real server-side controlA basic JWT setup is easy to get working, but it becomes less useful if you cannot revoke sessions or manage multiple devices cleanly. What I changed:
The important shift here was moving from “token exists, therefore session is valid” to “token exists, matches the database record, and has not been revoked or replaced.” That gave the server the authority to invalidate old sessions immediately. Why this fixed it:
This is what made logout-all and multi-device management work in a real way instead of just being cosmetic UI actions. 3. User data isolation had to be explicitBecause this is a multi-user app, I had to be careful that one account could never accidentally see another account’s wardrobe data. What I changed:
In practice, this meant every route had to ask the same question: “Does this user own the resource they are trying to access?” If the answer was no, the request stopped immediately. Why this fixed it:
That combination is what kept wardrobe data, feedback history, and images separated correctly across accounts. 4. Docker made the project easier to share, but only after the stack was organizedThe app includes the frontend, backend, Redis, Celery worker, and Celery Beat, so the first challenge was making the setup feel reproducible instead of fragile. What I changed:
This removed a lot of setup ambiguity. Instead of asking someone to manually figure out how the frontend, backend, Redis, and workers fit together, I made the stack describe itself. Why this fixed it:
That was important because the app depends on several moving parts, and the simplest way to make the project approachable was to make startup behavior predictable. What I LearnedThis project taught me a few important lessons:
I also learned that a project does not need to be huge to be useful. A focused app that solves one problem well can still feel meaningful. What I Want to Improve NextMy roadmap from here:
Future ImprovementsThere are still a few things I would like to add later:
ConclusionThis project began as a personal frustration and turned into a full web application with authentication, wardrobe storage, recommendation logic, and feedback infrastructure. The most rewarding part was seeing how practical software decisions, not just flashy UI, can help people make everyday choices faster. If you want to explore or run the project, check out the repo. You can try the flows and share feedback. I would especially love input on recommendation quality, UX clarity, and what features would make this genuinely useful in daily life. |




