Building Tryora: An AI-Powered 3D Virtual Try-On Platform
Published September 5, 2026
- nextjs
- express
- fastapi
- redis
- celery
- prisma
- system-design
- ai
Building Tryora: An AI-Powered 3D Virtual Try-On Platform
One problem has bugged online shoppers for as long as online shopping has existed: you can't tell how a dress will actually look on you until it shows up at your door. Product photos are shot on models who don't look like you, in lighting and poses you can't control, and by the time you find out it doesn't work, the return window is the only thing standing between you and a wasted purchase.
Tryora is my attempt at closing that gap — a platform where you upload a few photos of yourself, get a photorealistic 3D avatar, describe the outfit you're picturing in plain language, and see yourself wearing it in an AI-generated scene, all from the browser.
It's also the most ambitious system I've built to date, and the one that taught me the most about designing for failure, not just for the happy path.
The core problem: everything slow lives behind an API
The moment I sketched out what Tryora needed to do, one thing was obvious: almost none of it is fast. Reconstructing a 3D avatar from photos, running a virtual try-on model, generating a background scene with diffusion — these are GPU-bound jobs that can take anywhere from 10 to 60 seconds. A typical request/response API can't hold that door open. Long-polling connections time out, users bounce, and servers fall over under load.
So the architecture had to be asynchronous from day one. When a user submits photos or a try-on request, the API gateway doesn't do the work itself — it enqueues a job in Redis and immediately returns a 202 Accepted with a job ID. A Celery worker on the AI side picks it up, does the heavy lifting, writes the result to S3, and updates a job-status row in Postgres. The client just polls a lightweight status endpoint until it flips to COMPLETED. Nothing expensive ever sits on the request thread.
Three services, one schema
Tryora ended up as three cooperating services:
- Next.js on the frontend, rendering the 3D avatar viewer with React Three Fiber and handling the PWA layer.
- Express.js as a single API gateway — auth, sessions, rate limiting, and Redis-backed caching. Next.js never talks to the AI service directly; Express is the only front door.
- FastAPI on the Python side, owning everything GPU- and LLM-related: the avatar pipeline, the virtual try-on model, LLM-based dress search, and the Celery workers.
The trickiest part of that split wasn't the services themselves, it was keeping their data models in sync. Node and Python each have their own idioms for talking to a database, and schema drift between two languages is a classic way to introduce subtle bugs. I settled on a singleschema.prismafile as the one source of truth, generating both a Node Prisma client for Express and a Python Prisma client for FastAPI from it. Whenever the schema changes, both services regenerate from the same file — there's no window where one service's idea of aUsermodel can quietly diverge from the other's.
Making dress search actually understand what you mean
A user doesn't search for "floral maxi dress, yellow and light blue." They type something like "beach wedding dress." Turning that into something a database or search API can use is its own small AI problem: FastAPI passes the query to an LLM, which extracts structured parameters — style, event, colors — as JSON. Express checks Redis first for a cached result on those parameters; on a miss, FastAPI queries Postgres, and if that's empty too, it falls back to scraping search results live via the Serper API before caching the response for the next person who asks something similar.
Redis ends up doing double duty here — it's both the cache in front of dress search and the FIFO broker feeding Celery jobs. Using one piece of infrastructure for two different jobs kept the stack smaller, though it did mean being deliberate about key naming and eviction policy so the two uses didn't step on each other.
Making it work when the network doesn't
Because avatars and generated scenes are things people want to revisit — their wardrobe, their favorites — Tryora is a PWA with real offline support, not just an offline splash screen. A service worker intercepts fetches for .glb/.gltf model files and dress thumbnails and serves them from Cache Storage when there's no connection. The dress catalog and saved favorites live in IndexedDB. Mutations made offline, like unsaving a dress, apply optimistically in the UI and get queued for Background Sync, replaying automatically the moment the device reconnects.
What this project actually taught me
The individual pieces — a Next.js frontend, an Express API, a Python AI service — are all things I'd worked with before in isolation. What Tryora forced me to get right was the seams between them: designing a job queue so a slow GPU task can't take down a web server, keeping two languages honest about a shared schema, and treating "no internet" as a real state to design for rather than an edge case to ignore.
It's still evolving, but it's the project that's shifted how I think about building systems: less "does this feature work," more "what happens to this system under load, under latency, and when the network disappears."
Tryora is open source — the code and architecture details are on GitHub.