Every developer eventually needs to store and serve files. The default solution for years has been Amazon S3, but its pricing model, especially the dreaded "egress fees" for data transfer out, can be unpredictable and costly. Cloudflare R2 offers a compelling alternative: S3-compatible object storage with zero egress fees.
To put it to the test, I built a simple, secure, and completely serverless file-sharing application. This article breaks down the architecture that allows users to upload a file and get a temporary, shareable link, all powered by the Cloudflare stack.
The Challenge: Costly and Complex File Hosting
Building a file-sharing service traditionally requires:
- An object storage bucket (like S3) to hold the files.
- A backend server (e.g., Express.js, Django) to handle upload logic, generate signed URLs, and manage access control.
- Paying for server uptime, data storage, and every single byte downloaded by users (egress).
- Complex IAM policies to ensure files are uploaded and accessed securely.
This is a lot of overhead for what should be a simple task.
The Serverless Solution: Cloudflare Workers + R2
By combining Cloudflare Workers and R2, we can eliminate the backend server and the egress fees entirely. R2 is S3-compatible, but when accessed from a Cloudflare Worker, data transfer is free.
The Architecture
The entire workflow is a model of simplicity:
- A user selects a file on the static HTML frontend.
- JavaScript on the page makes a
POST
request to a Cloudflare Worker endpoint (e.g.,/api/upload
) with the file data. - The Worker receives the file and uses its native R2 binding to stream the file directly into an R2 bucket. It can also add metadata, like an expiry time.
- Upon successful upload, the Worker generates a unique key for the file and returns a shareable link to the user (e.g.,
/download/
). - When someone visits the shareable link, another Worker endpoint handles the download, fetching the file from R2 and streaming it back to the user.
The key benefit? The file is uploaded and downloaded through the Cloudflare network, which means $0 in egress fees, no matter how many times the file is downloaded.
Diving into the Code: The R2 Worker
The core of this application is a Cloudflare Worker with a binding to an R2 bucket. Here's how simple the upload logic can be:
// Simplified worker for handling file uploads
export default {
async fetch(request, env, ctx) {
if (request.method === 'POST') {
const url = new URL(request.url);
const objectKey = `uploads/${crypto.randomUUID()}-${url.searchParams.get('name')}`;
// Put the file from the request body into the R2 bucket
const object = await env.FILE_BUCKET.put(objectKey, request.body, {
httpMetadata: {
contentType: request.headers.get('content-type'),
},
});
// Return the key for the user to create a shareable link
return new Response(JSON.stringify({ key: objectKey }), {
headers: { 'Content-Type': 'application/json' },
});
}
// ... logic for handling GET requests to download would go here
return new Response('Not found', { status: 404 });
}
};
In this snippet, `env.FILE_BUCKET` is the binding to our R2 bucket, configured in the Cloudflare dashboard. The `.put()` method is all it takes to store the file.
The Final Result
This project proves that you can build robust, high-performance applications with features that were once complex and expensive, for a fraction of the cost—or often, for free.
- Zero Egress Fees: The biggest win. Serve terabytes of data without a surprise bill.
- No Backend Server: The entire logic lives in a globally distributed, auto-scaling Worker.
- Simplified Security: No complex IAM roles. Access is controlled directly in the Worker code.
- S3 Compatibility: You can still use S3 tools and SDKs with R2 if you need to.
Cloudflare R2, combined with Workers, isn't just an S3 alternative; it's a paradigm shift for building data-intensive applications on the web.