Client Area

Cloudflare Workers and R2 Storage for DomainIndia Apps

ByDomain India Team·DomainIndia Engineering
6 min readPublished 21 Apr 2026Updated 23 Jun 2026767 views

In this article

  • 1When Workers make sense
  • 2Free-tier limits (2026)
  • 3Step 1 — Install Wrangler
  • 4Step 2 — Create a Worker
  • 5Step 3 — R2 object storage

Cloudflare Workers and R2 Storage for DomainIndia Apps

TL;DR
Cloudflare Workers run your JavaScript/TypeScript at 330+ edge locations — sub-50ms globally. Paired with R2 (zero-egress S3-compatible storage), you can offload images, static APIs, and auth-edge checks from your DomainIndia origin. This guide covers when to use Workers, a working example, and R2 integration.

When Workers make sense

Workers aren't a replacement for your DomainIndia hosting — they complement it. Good use cases:

  • Edge auth — validate JWT at the edge; reject unauthenticated requests before they hit your origin
  • A/B testing — route 10% of traffic to a different backend without touching your app
  • Image resize / optimise — serve correctly-sized images on-demand
  • Geolocation redirects — send India traffic to one origin, EU to another
  • Cache tiering — cache expensive API responses at edge
  • Webhook receivers — terminate third-party webhooks, forward compacted data to origin

Bad use cases: full apps with complex state, heavy CPU work, long-running jobs. For those, stick with your DomainIndia VPS.

Free-tier limits (2026)

  • 100,000 requests/day per worker (free plan)
  • 10 ms CPU time per request
  • 128 MB memory
  • 1 MB request/response size

Paid plan ($5/mo): 10M requests/month, 50 ms CPU, no artificial request limits.

R2: 10 GB storage + 1M Class A ops + 10M Class B ops free forever. Zero egress fees — this is R2's killer feature vs S3.

Step 1 — Install Wrangler

Wrangler is Cloudflare's CLI for Workers.

bash
npm install -g wrangler
wrangler login   # opens browser, auth with your Cloudflare account

Step 2 — Create a Worker

bash
mkdir edge-auth && cd edge-auth
wrangler init --yes --type=javascript

src/index.js:

javascript
export default {
  async fetch(request, env, ctx) {
    // 1. Edge auth — check JWT before hitting origin
    const auth = request.headers.get('Authorization');
    if (!auth?.startsWith('Bearer ')) {
      return new Response('Unauthorized', { status: 401 });
    }
    const token = auth.slice(7);
    const valid = await verifyJWT(token, env.JWT_SECRET);
    if (!valid) return new Response('Invalid token', { status: 401 });

    // 2. Forward to DomainIndia origin
    const url = new URL(request.url);
    url.hostname = 'origin.yourcompany.com';  // your DomainIndia VPS domain
    const originRequest = new Request(url, request);
    const response = await fetch(originRequest);

    // 3. Add edge headers
    const modified = new Response(response.body, response);
    modified.headers.set('X-Auth-At-Edge', 'true');
    modified.headers.set('X-User-Id', valid.userId);
    return modified;
  },
};

async function verifyJWT(token, secret) {
  const [h, p, s] = token.split('.');
  if (!h || !p || !s) return null;

  const msg = `${h}.${p}`;
  const key = await crypto.subtle.importKey(
    'raw',
    new TextEncoder().encode(secret),
    { name: 'HMAC', hash: 'SHA-256' },
    false,
    ['verify']
  );
  const sig = Uint8Array.from(
    atob(s.replace(/-/g, '+').replace(/_/g, '/')),
    c => c.charCodeAt(0)
  );
  const ok = await crypto.subtle.verify(
    'HMAC', key, sig, new TextEncoder().encode(msg)
  );
  if (!ok) return null;
  return JSON.parse(atob(p));
}

wrangler.toml:

toml
name = "edge-auth"
main = "src/index.js"
compatibility_date = "2026-04-01"

[vars]
# non-secret config here

[env.production]
routes = [
  { pattern = "api.yourcompany.com/*", zone_name = "yourcompany.com" }
]

Set secret:

bash
wrangler secret put JWT_SECRET
# paste secret, press Ctrl+D

Deploy:

bash
wrangler deploy

Within 30 seconds, api.yourcompany.com is served through the Worker globally.

Step 3 — R2 object storage

R2 is S3-compatible but with zero egress cost. Perfect for user uploads, backups, static assets.

Create a bucket:

bash
wrangler r2 bucket create uploads

In your Worker, bind the bucket in wrangler.toml:

toml
[[r2_buckets]]
binding = "UPLOADS"
bucket_name = "uploads"

Access from Worker:

javascript
export default {
  async fetch(request, env) {
    const url = new URL(request.url);

    if (request.method === 'PUT') {
      // Upload
      const key = url.pathname.slice(1); // "user-123/photo.jpg"
      await env.UPLOADS.put(key, request.body, {
        httpMetadata: { contentType: request.headers.get('Content-Type') },
      });
      return new Response('Uploaded', { status: 201 });
    }

    if (request.method === 'GET') {
      const key = url.pathname.slice(1);
      const object = await env.UPLOADS.get(key);
      if (!object) return new Response('Not found', { status: 404 });
      return new Response(object.body, {
        headers: { 'Content-Type': object.httpMetadata.contentType },
      });
    }

    return new Response('Method not allowed', { status: 405 });
  },
};

Connecting R2 from your DomainIndia app

Your DomainIndia VPS/shared app can upload directly to R2 using any S3 SDK. Credentials: Cloudflare Dashboard → R2 → Manage R2 API Tokens.

Node.js example (works identically from any hosting):

javascript
import { S3Client, PutObjectCommand } from '@aws-sdk/client-s3';

const s3 = new S3Client({
  region: 'auto',
  endpoint: `https://${ACCOUNT_ID}.r2.cloudflarestorage.com`,
  credentials: {
    accessKeyId: process.env.R2_ACCESS_KEY_ID,
    secretAccessKey: process.env.R2_SECRET_ACCESS_KEY,
  },
});

await s3.send(new PutObjectCommand({
  Bucket: 'uploads',
  Key: 'photo.jpg',
  Body: fileBuffer,
  ContentType: 'image/jpeg',
}));

Cost comparison — R2 vs S3 vs DomainIndia bandwidth

For an app serving 100 GB/month of images:

StorageCost (₹/mo @ 100 GB)Egress (₹/mo @ 100 GB out)
R2 (Cloudflare)₹125₹0 (free egress)
AWS S3₹210₹700 (first GB free, then $0.09/GB)
Backblaze B2₹50₹85 (3× stored free)
DomainIndia bandwidthIncluded in planCounts toward your plan quota

R2 wins for public-facing content. DomainIndia bandwidth is fine for low-traffic apps or internal use. B2 wins for pure backup storage with minimal downloads.

Durable Objects — stateful edge (advanced)

For chat apps, live collaboration, rate limiting by user: Durable Objects give you a single logical instance of state at the edge, serialised.

javascript
export class RateLimiter {
  constructor(state, env) {
    this.state = state;
  }
  async fetch(request) {
    const count = (await this.state.storage.get('count')) ?? 0;
    await this.state.storage.put('count', count + 1);
    return new Response(JSON.stringify({ count }));
  }
}

// Route to the DO in your Worker:
const id = env.RATE_LIMITER.idFromName(userId);
const obj = env.RATE_LIMITER.get(id);
return obj.fetch(request);

Each user has their own DO instance, automatically deployed near them.

Common pitfalls

FAQ

Q Does Workers replace my DomainIndia hosting?

No. Workers are for edge logic — small, fast, stateless. Your app, database, and long-running processes stay on DomainIndia.

Q Can Workers talk to my PostgreSQL on DomainIndia?

Not directly — Workers use fetch() only. Use a REST/GraphQL API on your VPS, or Cloudflare Hyperdrive (beta) which pools connections.

Q Is R2 reliable enough for production?

Yes. 99.9% SLA, 11 nines durability. Used by major SaaS companies. Pair with lifecycle rules for automatic cold-storage migration.

Q Can I serve a full website from Workers?

Cloudflare Pages is better for static sites + Functions. Workers are more for API edge logic. Use DomainIndia for your main app, Workers for the thin edge layer.

Q How do I debug Workers in production?

wrangler tail --format json streams logs live. Add to Logpush for long-term retention in R2.

Use Cloudflare Workers in front of your DomainIndia hosting for global edge speed. See hosting plans

Was this article helpful?

Your feedback helps us improve our documentation

Still need help? Submit a support ticket