<?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>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>
