<?xml version="1.0" encoding="UTF-8"?>
<rss version="2.0" xmlns:atom="http://www.w3.org/2005/Atom" xmlns:dc="http://purl.org/dc/elements/1.1/">
  <channel>
    <title>DEV Community: ihddirmas</title>
    <description>The latest articles on DEV Community by ihddirmas (@ihddirmas).</description>
    <link>https://dev.to/ihddirmas</link>
    <image>
      <url>https://media2.dev.to/dynamic/image/width=90,height=90,fit=cover,gravity=auto,format=auto/https:%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Fuser%2Fprofile_image%2F3996945%2Fbab330e3-5f74-4d65-964f-8f620dc6b21c.png</url>
      <title>DEV Community: ihddirmas</title>
      <link>https://dev.to/ihddirmas</link>
    </image>
    <atom:link rel="self" type="application/rss+xml" href="https://dev.to/feed/ihddirmas"/>
    <language>en</language>
    <item>
      <title>How We Built StyleSense: AI Virtual Try-On Powered by Amazon Aurora &amp; Vercel</title>
      <dc:creator>ihddirmas</dc:creator>
      <pubDate>Mon, 29 Jun 2026 23:49:03 +0000</pubDate>
      <link>https://dev.to/ihddirmas/how-we-built-stylesense-ai-virtual-try-on-powered-by-amazon-aurora-vercel-dgf</link>
      <guid>https://dev.to/ihddirmas/how-we-built-stylesense-ai-virtual-try-on-powered-by-amazon-aurora-vercel-dgf</guid>
      <description>&lt;p&gt;Online clothing returns cost the industry over $816 billion a year. The root cause is simple: shoppers can't see how clothes look on &lt;em&gt;their&lt;/em&gt; body before buying. We built &lt;strong&gt;StyleSense&lt;/strong&gt; to fix that — an AI-powered virtual wardrobe where you upload a selfie, add clothes from any product URL, and instantly see yourself wearing them.&lt;/p&gt;

&lt;p&gt;Here's how we built it, and why Amazon Aurora PostgreSQL and Vercel were the right choices for a 72-hour hackathon build.&lt;/p&gt;




&lt;h2&gt;
  
  
  The Stack
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Frontend&lt;/strong&gt;: Next.js 14 App Router + TypeScript + Tailwind CSS, deployed on &lt;strong&gt;Vercel&lt;/strong&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Backend&lt;/strong&gt;: FastAPI (Python 3.12) with 50+ REST endpoints&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Primary database&lt;/strong&gt;: &lt;strong&gt;Amazon Aurora PostgreSQL Serverless v2&lt;/strong&gt; (ap-south-1)&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Auth + Realtime&lt;/strong&gt;: Supabase (JWT, Storage, social features)&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;AI&lt;/strong&gt;: Runway ML (try-on, video, voice avatar) + Anthropic Claude (stylist chat, vision)&lt;/li&gt;
&lt;/ul&gt;




&lt;h2&gt;
  
  
  Why Amazon Aurora PostgreSQL
&lt;/h2&gt;

&lt;p&gt;We needed a database that could handle:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;
&lt;strong&gt;Bursts of writes&lt;/strong&gt; during a live demo (multiple judges hitting Generate simultaneously)&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Zero idle cost&lt;/strong&gt; — we're not paying for credits when no one is testing&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;No secrets in code&lt;/strong&gt; — hackathon repos get shared, credentials shouldn't&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;Aurora Serverless v2 solved all three. It scales to zero between test runs, scales up instantly under load, and supports &lt;strong&gt;IAM authentication&lt;/strong&gt; — meaning our FastAPI backend never stores a database password.&lt;/p&gt;

&lt;h3&gt;
  
  
  IAM Auth in FastAPI — the key pattern
&lt;/h3&gt;

&lt;p&gt;Instead of a static &lt;code&gt;DATABASE_URL&lt;/code&gt; with a password, we use boto3 to mint a short-lived token at connection time:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight python"&gt;&lt;code&gt;&lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;boto3&lt;/span&gt;

&lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;get_iam_token&lt;/span&gt;&lt;span class="p"&gt;():&lt;/span&gt;
    &lt;span class="n"&gt;client&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;boto3&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;client&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;rds&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;region_name&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;ap-south-1&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="n"&gt;client&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;generate_db_auth_token&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
        &lt;span class="n"&gt;DBHostname&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;stylesense.cluster-c9cswq8kqykn.ap-south-1.rds.amazonaws.com&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="n"&gt;Port&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="mi"&gt;5432&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="n"&gt;DBUsername&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;stylesense_app&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="n"&gt;Region&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;ap-south-1&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="p"&gt;)&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;We wire this into SQLAlchemy's connection pool via a custom &lt;code&gt;creator&lt;/code&gt; function that refreshes the token before each new connection — tokens expire in 15 minutes, so this is essential:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight python"&gt;&lt;code&gt;&lt;span class="kn"&gt;from&lt;/span&gt; &lt;span class="n"&gt;sqlalchemy&lt;/span&gt; &lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;create_engine&lt;/span&gt;

&lt;span class="n"&gt;engine&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;create_engine&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
    &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;postgresql+psycopg2://&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="n"&gt;creator&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="k"&gt;lambda&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;psycopg2&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;connect&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
        &lt;span class="n"&gt;host&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="n"&gt;AURORA_HOST&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="n"&gt;port&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="mi"&gt;5432&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="n"&gt;dbname&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;stylesense&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="n"&gt;user&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;stylesense_app&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="n"&gt;password&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="nf"&gt;get_iam_token&lt;/span&gt;&lt;span class="p"&gt;(),&lt;/span&gt;
        &lt;span class="n"&gt;sslmode&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;require&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;No password in &lt;code&gt;.env&lt;/code&gt;. No password in CI. No password in the repo. Just an IAM role.&lt;/p&gt;

&lt;h3&gt;
  
  
  Schema design
&lt;/h3&gt;

&lt;p&gt;We kept it lean — 4 core tables in Aurora:&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Table&lt;/th&gt;
&lt;th&gt;What it stores&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;users&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;Selfie URLs, stylized avatar URLs, body analysis&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;wardrobe_items&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;Clothing images, categories, colors, source URLs&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;try_on_results&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;Generated try-on images, event scenes, video URLs&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;outfits&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;Saved outfit combinations&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;Auth and social (friendships, chat threads) stay in Supabase, which already has Row-Level Security and Realtime built in. Aurora handles the data that benefits from SQL joins and full ACID transactions — like "show me all try-ons for items in category X that this user saved as outfits."&lt;/p&gt;




&lt;h2&gt;
  
  
  Vercel: Zero-Config Frontend Deploys
&lt;/h2&gt;

&lt;p&gt;Our Next.js frontend deploys to Vercel with three environment variables and zero configuration files. The things that made the biggest difference:&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Edge middleware for auth&lt;/strong&gt;: Supabase session refresh runs at the edge on every request, so protected pages never flash unauthenticated content. This is one &lt;code&gt;middleware.ts&lt;/code&gt; file:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight typescript"&gt;&lt;code&gt;&lt;span class="c1"&gt;// middleware.ts&lt;/span&gt;
&lt;span class="k"&gt;import&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nx"&gt;createServerClient&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="k"&gt;from&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;@supabase/ssr&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="k"&gt;import&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nx"&gt;NextResponse&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="k"&gt;from&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;next/server&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

&lt;span class="k"&gt;export&lt;/span&gt; &lt;span class="k"&gt;async&lt;/span&gt; &lt;span class="kd"&gt;function&lt;/span&gt; &lt;span class="nf"&gt;middleware&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;request&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;NextRequest&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="c1"&gt;// Refreshes the Supabase session cookie on every request&lt;/span&gt;
  &lt;span class="c1"&gt;// Redirects to /login if the user is not authenticated&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;strong&gt;Mumbai region&lt;/strong&gt;: We deployed both the Vercel frontend (&lt;code&gt;bom1&lt;/code&gt;) and Aurora (&lt;code&gt;ap-south-1&lt;/code&gt;) in Mumbai, keeping API latency under 20ms for the database round-trip.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;The one gotcha&lt;/strong&gt;: Runway ML requires all image URLs to be public HTTPS. Localhost URLs fail silently. Every selfie and wardrobe image gets re-hosted to Supabase Storage before being passed to any Runway API call — Vercel's environment variables made switching between dev and prod URLs painless.&lt;/p&gt;




&lt;h2&gt;
  
  
  The AI Pipeline
&lt;/h2&gt;

&lt;p&gt;The "wow moment" flow that we demoed:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;Upload selfie → Claude vision detects body type&lt;/li&gt;
&lt;li&gt;Paste Amazon URL → scraper extracts product image → Runway garment cleaner isolates the clothing&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Try-on&lt;/strong&gt;: Runway gen4_image composites the garment onto your selfie (~10s, 5 credits)&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Event scene&lt;/strong&gt;: Runway gen4_image places you in a "beach wedding" or any setting you describe&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Animate&lt;/strong&gt;: Runway gen4.5 turns the still into a 5-second runway walk video (~60s, 60-100 credits)&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Voice stylist&lt;/strong&gt;: Aria (a Runway Characters API avatar) answers questions about your wardrobe in real time — her knowledge base is synced from Aurora at every session start&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;The entire pipeline is async. FastAPI fires each Runway task and returns a task ID; the frontend polls via Zustand store state that survives page navigation.&lt;/p&gt;




&lt;h2&gt;
  
  
  Challenges
&lt;/h2&gt;

&lt;p&gt;&lt;strong&gt;Aurora IAM token refresh in connection pools&lt;/strong&gt;: SQLAlchemy's connection pool reuses connections, so a token minted at startup expires mid-session. The fix: use &lt;code&gt;NullPool&lt;/code&gt; or override the &lt;code&gt;creator&lt;/code&gt; so a fresh token is minted for every logical connection. We went with the &lt;code&gt;creator&lt;/code&gt; approach and a short pool recycle time.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Runway image URL requirement&lt;/strong&gt;: Every image URL sent to Runway must be a public HTTPS URL. During development this means all local uploads get re-hosted to Supabase Storage before the Runway call — an extra round-trip but essential.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Credit budget discipline&lt;/strong&gt;: Runway gen4.5 video costs 60-100 credits per 5 seconds. With a 50,000-credit budget, we used gen4_image_turbo (2 credits) for all dev testing and reserved the full quality models for demo recordings.&lt;/p&gt;




&lt;h2&gt;
  
  
  What We'd Do Differently
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Aurora DSQL&lt;/strong&gt; would eliminate connection management entirely — it's a serverless SQL database with no instance to configure. For a pure read-heavy workload like wardrobe browsing, it would be a better fit.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;DynamoDB&lt;/strong&gt; for the task queue — our current in-memory task store resets on server restart. DynamoDB TTL would give us persistent background task history.&lt;/li&gt;
&lt;/ul&gt;




&lt;h2&gt;
  
  
  Try It
&lt;/h2&gt;

&lt;p&gt;The full source is on GitHub. The live demo runs on Vercel at [&lt;a href="https://style-sense-steel.vercel.app/" rel="noopener noreferrer"&gt;https://style-sense-steel.vercel.app/&lt;/a&gt;].&lt;/p&gt;

&lt;p&gt;Built in 72 hours for the &lt;strong&gt;H0: Hack the Zero Stack with Vercel v0 and AWS Databases&lt;/strong&gt; hackathon.&lt;/p&gt;

&lt;p&gt;&lt;em&gt;#H0Hackathon&lt;/em&gt;&lt;/p&gt;




</description>
      <category>ai</category>
      <category>aws</category>
      <category>showdev</category>
      <category>webdev</category>
    </item>
  </channel>
</rss>
