<?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: tabban Ghani</title>
    <description>The latest articles on DEV Community by tabban Ghani (@tabban_ghani_).</description>
    <link>https://dev.to/tabban_ghani_</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%2F3858257%2Ffde5e5cd-0d27-462d-8dfb-60ed14cdf0c1.png</url>
      <title>DEV Community: tabban Ghani</title>
      <link>https://dev.to/tabban_ghani_</link>
    </image>
    <atom:link rel="self" type="application/rss+xml" href="https://dev.to/feed/tabban_ghani_"/>
    <language>en</language>
    <item>
      <title>SurplusLink AI Turning Surplus Bakery Inventory Into a B2B Side Hustle</title>
      <dc:creator>tabban Ghani</dc:creator>
      <pubDate>Mon, 19 Oct 2026 02:07:42 +0000</pubDate>
      <link>https://dev.to/tabban_ghani_/surpluslink-ai-turning-surplus-bakery-inventory-into-a-b2b-side-hustle-47ne</link>
      <guid>https://dev.to/tabban_ghani_/surpluslink-ai-turning-surplus-bakery-inventory-into-a-b2b-side-hustle-47ne</guid>
      <description>&lt;p&gt;&lt;em&gt;This is a submission for the &lt;a href="https://dev.to/challenges/hacktoberfest-week2-2026-10-12"&gt;Hacktoberfest Open-Source AI Challenge Week 2: Side Hustle&lt;/a&gt;&lt;/em&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  What I Built
&lt;/h2&gt;

&lt;p&gt;Every day, food manufacturers, bakeries and wholesalers face a familiar business challenge: having inventory they need to sell before it loses commercial value.&lt;br&gt;
At the same time, small retailers are searching for affordable stock but may lack direct access to suitable wholesale suppliers.&lt;/p&gt;

&lt;h2&gt;
  
  
  What if AI could help these businesses discover  more easily?
&lt;/h2&gt;

&lt;p&gt;That question inspired SurplusLink AI, an AI-assisted B2B marketplace prototype focused on surplus packaged bakery products in Pakistan.&lt;/p&gt;

&lt;h2&gt;
  
  
  The idea is simple:
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;Wholesalers list available inventory, retailers describe what they need, and the platform connects compatible supply and demand.&lt;/li&gt;
&lt;li&gt;Instead of becoming another generic chatbot, SurplusLink uses an open-weight language model for a specific business task: converting informal product descriptions into structured marketplace information.&lt;/li&gt;
&lt;li&gt;The initial product scope includes sealed packaged goods such as biscuits, rusks, crackers and cookies. Real-world deployment would require appropriate product verification, expiry controls and food-safety compliance.&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  Who would pay for this?
&lt;/h2&gt;

&lt;p&gt;The intended customers are wholesalers who want to recover value from excess inventory and retailers who want to source products at competitive prices.&lt;/p&gt;

&lt;h2&gt;
  
  
  The core workflow
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;A wholesaler lists product details, quantity, asking price, location and expiry information.&lt;/li&gt;
&lt;li&gt;A retailer enters what they need using natural language.&lt;/li&gt;
&lt;li&gt;AI extracts structured information:An open-weight language model identifies relevant product attributes.&lt;/li&gt;
&lt;li&gt;The backend evaluates compatible inventory using deterministic business rules.&lt;/li&gt;
&lt;li&gt;Buyers submit offers that sellers can accept or reject.&lt;/li&gt;
&lt;li&gt;Accepted offers reserve inventory and generate transaction records.
The prototype also explores a simulated checkout process and a transaction-based marketplace revenue model.&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  Demo
&lt;/h2&gt;

&lt;p&gt;&lt;a href="https://www.youtube.com/watch?v=gDrjds6meFg" rel="noopener noreferrer"&gt;https://www.youtube.com/watch?v=gDrjds6meFg&lt;/a&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  Code
&lt;/h2&gt;

&lt;p&gt;&lt;a href="https://github.com/taba123-debug/SurplusLink-AI-frontend" rel="noopener noreferrer"&gt;https://github.com/taba123-debug/SurplusLink-AI-frontend&lt;/a&gt;&lt;br&gt;
&lt;a href="https://github.com/taba123-debug/SurplusLink-AI-backend/" rel="noopener noreferrer"&gt;https://github.com/taba123-debug/SurplusLink-AI-backend/&lt;/a&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  How I Built It
&lt;/h2&gt;

&lt;h2&gt;
  
  
  1.Designing the marketplace around real business entities:
&lt;/h2&gt;

&lt;p&gt;I started by treating SurplusLink as a marketplace rather than an AI chat application.The backend data model covers organizations, business memberships, surplus inventory, buyer requirements, offers, deals, payments and transaction events.&lt;br&gt;
PostgreSQL manages these relationships, while SQLAlchemy and Alembic provide database models and versioned migrations.&lt;/p&gt;

&lt;h2&gt;
  
  
  One important inventory rule is:
&lt;/h2&gt;

&lt;p&gt;Available Quantity = Total Quantity - Reserved Quantity - Sold Quantity.This separation makes it possible to track stock committed to accepted deals without confusing it with inventory that has already been collected.&lt;/p&gt;

&lt;h2&gt;
  
  
  2.AI-assisted inventory understanding
&lt;/h2&gt;

&lt;p&gt;One of the main usability problems in a marketplace is that sellers and buyers describe products differently.&lt;br&gt;
For example, a retailer might write:&lt;br&gt;
I need 150 biscuit packs in Lahore, under Rs. 20,000, with at least 30 days of shelf life.&lt;/p&gt;

&lt;h2&gt;
  
  
  The AI extraction layer is designed to  information such as:
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;Product category: BISCUITS&lt;/li&gt;
&lt;li&gt;Quantity: 150&lt;/li&gt;
&lt;li&gt;Unit: PACK&lt;/li&gt;
&lt;li&gt;Maximum budget: Rs. 20,000&lt;/li&gt;
&lt;li&gt;City: Lahore&lt;/li&gt;
&lt;li&gt;Minimum remaining shelf life: 30 days&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;The extracted values are validated using Pydantic before they can be used as structured marketplace information.Missing fields are not supposed to be guessed. Users review and confirm extracted details before saving them.I moved toward a hosted open-weight Llama model through GroqCloud to avoid downloading model weights onto my development laptop.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2F6k4i8ktd5s12amg97oqr.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2F6k4i8ktd5s12amg97oqr.png" alt=" " width="800" height="533"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  3.Two-way inventory matching
&lt;/h2&gt;

&lt;p&gt;SurplusLink is designed to work in both directions.A buyer can discover seller listings that match a saved purchasing requirement.A seller can discover buyer requirements that are compatible with an available inventory lot.Rather than delegating every decision to an LLM, the matching engine uses explicit rules such as product category, unit, quantity, city, expiry, shelf life and price.&lt;/p&gt;

&lt;p&gt;The AI interprets the user's language. The backend decides whether the structured records are compatible.&lt;br&gt;
This separation keeps financial and inventory decisions understandable and testable.&lt;/p&gt;

&lt;h2&gt;
  
  
  4.Offers and inventory consistency
&lt;/h2&gt;

&lt;p&gt;After finding a potential match, a buyer can submit an offer for a chosen quantity and price.The seller then decides whether to accept it.An accepted offer becomes a deal. The backend is designed to validate inventory availability at acceptance time and reserve stock transactionally, avoiding duplicate allocation of the same units.&lt;br&gt;
For a marketplace, this is especially important: a product may attract multiple buyers at the same time.&lt;/p&gt;

&lt;h2&gt;
  
  
  5.Simulating the revenue model
&lt;/h2&gt;

&lt;p&gt;SurplusLink is intended to generate revenue through transaction fees rather than monthly subscriptions.&lt;br&gt;
The proposed fee model is:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;2% buyer service fee&lt;/li&gt;
&lt;li&gt;2% seller commission
For example, consider a wholesale deal for 150 biscuit packs at Rs. 120 each.&lt;/li&gt;
&lt;li&gt;Financial breakdown&lt;/li&gt;
&lt;li&gt;Amount&lt;/li&gt;
&lt;li&gt;Transaction subtotal&lt;/li&gt;
&lt;li&gt;Rs. 18,000&lt;/li&gt;
&lt;li&gt;Buyer fee (2%)&lt;/li&gt;
&lt;li&gt;Rs. 360&lt;/li&gt;
&lt;li&gt;Buyer pays&lt;/li&gt;
&lt;li&gt;Rs. 18,360&lt;/li&gt;
&lt;li&gt;Seller fee (2%&lt;/li&gt;
&lt;li&gt;Rs. 360&lt;/li&gt;
&lt;li&gt;Seller receives&lt;/li&gt;
&lt;li&gt;Rs. 17,640&lt;/li&gt;
&lt;li&gt;Platform gross fees&lt;/li&gt;
&lt;li&gt;Rs. 720&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;This is an illustrative business scenario. It is not real income, and the prototype does not process real financial transactions.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2F5w3f16sce9zy5zm33isb.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2F5w3f16sce9zy5zm33isb.png" alt=" " width="800" height="1200"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  Why Does Open Innovation Matter?
&lt;/h2&gt;

&lt;p&gt;Open-weight AI made this project practical to explore without training an entire language model from scratch.&lt;br&gt;
For a small marketplace product, the valuable part is not necessarily building the largest model. It is applying an existing model to a specific problem and combining its output with reliable software engineering.&lt;/p&gt;

&lt;h2&gt;
  
  
  The benefits of this approach include:
&lt;/h2&gt;

&lt;h2&gt;
  
  
  Model flexibility:
&lt;/h2&gt;

&lt;p&gt;The AI integration can be isolated behind a provider interface, making it easier to experiment with alternative compatible models.&lt;/p&gt;

&lt;h2&gt;
  
  
  Lower infrastructure requirements:
&lt;/h2&gt;

&lt;p&gt;Hosted inference allows development without maintaining a dedicated local GPU or running large model weights.&lt;/p&gt;

&lt;h2&gt;
  
  
  More control over business logic:
&lt;/h2&gt;

&lt;p&gt;AI assists with interpretation, while validated schemas, SQL queries and transaction rules determine what the system actually does.&lt;/p&gt;

&lt;h2&gt;
  
  
  A path toward specialization:
&lt;/h2&gt;

&lt;p&gt;In the future, a model could be evaluated or fine-tuned for local wholesale inventory descriptions, including Urdu-English terminology and product abbreviations.&lt;br&gt;
Using an open-weight model does not automatically make an application private, offline or free to operate. Hosted inference still has provider costs and data-handling considerations, which would need to be evaluated before onboarding real businesses.&lt;/p&gt;

&lt;h2&gt;
  
  
  My Agent Session
&lt;/h2&gt;

&lt;p&gt;During the development of SurplusLink AI, I used OpenAI Codex as an AI-assisted development tool.&lt;br&gt;
Codex helped accelerate the implementation of FastAPI endpoints, PostgreSQL database models, Alembic migrations, AI inference integration, and frontend connectivity.&lt;br&gt;
My approach was to define the marketplace architecture, business workflows, and technical requirements first, then use Codex to implement and refine individual components.&lt;br&gt;
This allowed me to focus on understanding backend architecture, inventory management, transaction handling, and the integration of open-weight AI into a practical business application.&lt;/p&gt;

&lt;h2&gt;
  
  
  Closing Thoughts
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;SurplusLink AI explores a straightforward idea:&lt;/li&gt;
&lt;li&gt;Excess inventory for one business could become a useful purchasing opportunity for another.&lt;/li&gt;
&lt;li&gt;By combining open-weight AI for natural-language understanding with conventional marketplace architecture, I wanted to build something that has a plausible path from hackathon prototype to a practical side hustle.&lt;/li&gt;
&lt;li&gt;Thanks to the Hacktoberfest and DEV Community organizers for creating this opportunity to experiment with open-source AI.&lt;/li&gt;
&lt;li&gt;I'd love feedback on the idea, especially from developers building B2B marketplaces or AI-powered tools for small businesses.&lt;/li&gt;
&lt;/ul&gt;

</description>
      <category>devchallenge</category>
      <category>hf26challenge</category>
      <category>ai</category>
      <category>webdev</category>
    </item>
    <item>
      <title>How is R2 used for object storage?</title>
      <dc:creator>tabban Ghani</dc:creator>
      <pubDate>Wed, 30 Sep 2026 07:48:55 +0000</pubDate>
      <link>https://dev.to/tabban_ghani_/how-is-r2-used-for-object-storage-3ppd</link>
      <guid>https://dev.to/tabban_ghani_/how-is-r2-used-for-object-storage-3ppd</guid>
      <description>&lt;p&gt;Modern applications often need to store profile pictures, PDFs, documents, videos, and other media. Instead of storing these files directly in a database, we can use &lt;strong&gt;object storage&lt;/strong&gt;.&lt;/p&gt;

&lt;p&gt;Let's understand the complete flow with a simple example: &lt;strong&gt;uploading a profile picture&lt;/strong&gt;.&lt;/p&gt;

&lt;h2&gt;
  
  
  R2 and the Database
&lt;/h2&gt;

&lt;p&gt;Suppose a user wants to upload:&lt;/p&gt;

&lt;p&gt;&lt;code&gt;profile-image.png&lt;br&gt;
&lt;/code&gt;&lt;/p&gt;

&lt;p&gt;We separate the actual file from its application metadata:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;DATABASE                         CLOUDFLARE R2
────────                         ─────────────
userId                           Actual image
fileKey  ──────────────────────&amp;gt; users/123/profile/abc.png
fileName
mimeType
fileSize
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fwv9wdp86tkf28ltfrwth.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fwv9wdp86tkf28ltfrwth.png" alt=" " width="800" height="450"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;The actual image is stored in R2, while our database stores information needed to reference and manage it.&lt;/strong&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  How Does the Upload Work?
&lt;/h2&gt;

&lt;p&gt;The basic architecture is:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Frontend
   │
   │ 1. Request upload
   ▼
Backend
   │
   │ 2. Validate file information
   │ 3. Generate unique fileKey
   │ 4. Generate Presigned PUT URL
   ▼
Frontend
   │
   │ 5. PUT actual file
   ▼
Cloudflare R2
   │
   ▼
Object Stored
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  1. Frontend requests an upload
&lt;/h3&gt;

&lt;p&gt;The frontend first sends information about the selected file:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight http"&gt;&lt;code&gt;&lt;span class="err"&gt;POST /media/upload-url
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;





&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight json"&gt;&lt;code&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"fileName"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"profile-image.png"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"mimeType"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"image/png"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"fileSize"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mi"&gt;245760&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;At this point, the actual image has &lt;strong&gt;not&lt;/strong&gt; been uploaded.&lt;/p&gt;

&lt;h3&gt;
  
  
  2. Backend validates and generates a file key
&lt;/h3&gt;

&lt;p&gt;The backend validates information such as the allowed file type and size.&lt;/p&gt;

&lt;p&gt;It then generates a unique object key:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;users/123/profile/550e8400.png
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This &lt;code&gt;fileKey&lt;/code&gt; identifies where the object will be stored inside the R2 bucket.&lt;/p&gt;

&lt;h3&gt;
  
  
  3. Backend generates a Presigned PUT URL
&lt;/h3&gt;

&lt;p&gt;Using server-side R2 credentials, the backend generates a temporary &lt;strong&gt;presigned PUT URL&lt;/strong&gt; for that specific object key.&lt;/p&gt;

&lt;p&gt;For example:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight json"&gt;&lt;code&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"fileKey"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"users/123/profile/550e8400.png"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"uploadUrl"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"&amp;lt;presigned-url&amp;gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"expiresIn"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mi"&gt;600&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Conceptually, the URL provides temporary permission for:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Operation → PUT
Object    → users/123/profile/550e8400.png
Expiry    → 10 minutes
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The R2 credentials remain securely on the backend.&lt;/p&gt;

&lt;h3&gt;
  
  
  4. Frontend uploads directly to R2
&lt;/h3&gt;

&lt;p&gt;The frontend now sends the actual image using the presigned URL:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight http"&gt;&lt;code&gt;&lt;span class="err"&gt;PUT &amp;lt;presigned-url&amp;gt;
Content-Type: image/png

&amp;lt;actual image bytes&amp;gt;
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The important architecture is:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Frontend ───── actual file ─────&amp;gt; R2
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The large file doesn't need to travel through the application backend in this direct-upload design.&lt;/p&gt;

&lt;p&gt;R2 validates the signed request and stores the object under the key used to create the URL.&lt;/p&gt;

&lt;h3&gt;
  
  
  5. Store the reference
&lt;/h3&gt;

&lt;p&gt;After a successful upload, the application can save or finalize the metadata:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight yaml"&gt;&lt;code&gt;&lt;span class="na"&gt;userId&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;    &lt;span class="m"&gt;123&lt;/span&gt;
&lt;span class="na"&gt;fileKey&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;   &lt;span class="s"&gt;users/123/profile/550e8400.png&lt;/span&gt;
&lt;span class="na"&gt;fileName&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;  &lt;span class="s"&gt;profile-image.png&lt;/span&gt;
&lt;span class="na"&gt;mimeType&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;  &lt;span class="s"&gt;image/png&lt;/span&gt;
&lt;span class="na"&gt;fileSize&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;  &lt;span class="m"&gt;245760&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;A more robust application can also call something like:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight http"&gt;&lt;code&gt;&lt;span class="err"&gt;POST /media/complete
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;after the R2 upload succeeds, allowing the backend to verify/finalize the uploaded media.&lt;/p&gt;

&lt;h2&gt;
  
  
  What Happens When the Presigned URL Expires?
&lt;/h2&gt;

&lt;p&gt;Suppose the URL is valid for 10 minutes.&lt;/p&gt;

&lt;p&gt;After 10 minutes:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Presigned URL       ❌ Expired
Uploaded R2 object  ✅ Still exists
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The &lt;strong&gt;permission is temporary, not the stored object&lt;/strong&gt;.&lt;/p&gt;

&lt;h2&gt;
  
  
  Final Flow
&lt;/h2&gt;

&lt;p&gt;The entire upload process can be remembered as:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Select File
    ↓
POST metadata → Backend
    ↓
Validate
    ↓
Generate File Key
    ↓
Generate Presigned PUT URL
    ↓
Return URL + File Key
    ↓
Frontend PUT → R2
    ↓
Actual File Stored
    ↓
Store / Finalize Metadata
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The core idea is simple:&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;The backend controls the upload, the presigned URL provides temporary permission, the frontend uploads directly to R2, R2 stores the actual file, and the database stores its reference.&lt;/strong&gt;&lt;/p&gt;

</description>
      <category>webdev</category>
      <category>programming</category>
      <category>ai</category>
      <category>productivity</category>
    </item>
  </channel>
</rss>
