# 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:
| Storage | Cost (₹/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 bandwidth | Included in plan | Counts 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