1. Pengenalan Cloudflare Workers
Cloudflare Workers adalah platform serverless computing yang memungkinkan Anda menjalankan JavaScript (dan WebAssembly) di edge β tepat di depan user, di lebih dari 300 lokasi di seluruh dunia. Tidak seperti serverless tradisional yang berjalan di satu atau beberapa region, Workers berjalan di setiap server Cloudflare secara global.
Workers menggunakan V8 JavaScript engine β engine yang sama dengan Google Chrome dan Node.js β tetapi dioptimasi untuk edge computing. Setiap Worker berjalan dalam isolasi penuh dengan startup time yang sangat cepat (<5ms cold start), menjadikannya ideal untuk latency-sensitive applications.
Mengapa Edge Computing Penting?
| Keunggulan | Penjelasan |
|---|---|
| Latency Rendah | Code berjalan di dekat user β response time <50ms global |
| 300+ Locations | Setiap PoP Cloudflare bisa menjalankan Worker Anda |
| No Cold Start | V8 isolates β startup <5ms, tidak seperti Lambda (50-250ms) |
| Deterministic Pricing | Harga flat β tidak perlu khawatir billing surprise |
| Full Web Standards | Fetch API, Web Crypto, Streams, WebSocket β semua standar web |
| Eksosistem Lengkap | KV, R2, D1, Durable Objects, Queues, AI β semua terintegrasi |
TRADITIONAL SERVERLESS (Lambda) CLOUDFLARE WORKERS
ββββββββββββ ββββββββββββββββββββββββ
β User β β 300+ Edge PoPs β
β (Jakarta)β β β
ββββββ¬ββββββ β ββββββββ ββββββββ β
β β β PoP β β PoP β β
βΌ β β JKT β β SIN β β
ββββββββββββ β βWorkerβ βWorkerβ β
β Lambda β β Single Region β ββββββββ ββββββββ β
β (us-east)β ~150ms latency β ββββββββ ββββββββ β
ββββββββββββ β β PoP β β PoP β β
β β FRA β β SFO β β
β βWorkerβ βWorkerβ β
β ββββββββ ββββββββ β
β β
β ~20ms latency β
ββββββββββββββββββββββββ
β 150ms latency dari Jakarta β
~20ms latency (local PoP)
β Cold start 50-250ms β
Cold start <5ms
β Harga per request + duration β
Harga flat per request
Pricing
| Plan | Free | Paid ($5/bulan) |
|---|---|---|
| Requests | 100,000/hari | 10 juta/bulan, $0.30/juta setelahnya |
| CPU Time | 10ms/request | 30 detik/request (unlimited total) |
| KV Reads | 100,000/hari | 10 juta/bulan, $0.50/juta |
| R2 Storage | 10 GB | 10 GB gratis, $0.015/GB/bulan |
| D1 Storage | 5 GB | 5 GB, $0.75/juta read |
Cloudflare Workers free tier sangat generous β 100,000 request per hari tanpa biaya. Cukup untuk banyak production workload kecil-menengah. Bayar $5/bulan untuk Workers Paid plan dan dapatkan 10 juta request termasuk.
2. Memulai dengan Workers
Instalasi Wrangler CLI
# Install Wrangler CLI (Cloudflare's CLI tool) npm install -g wrangler # Login ke Cloudflare account wrangler login # Verifikasi wrangler whoami
Membuat Project Pertama
# Buat project dari template npm create cloudflare@latest my-first-worker # Pilih: # - What would you like to start with? Hello World example # - Which template? Hello World Worker # - Language? TypeScript # - Deploy with Cloudflare? Yes cd my-first-worker # Lihat struktur project ls -la # src/index.ts β main Worker code # wrangler.toml β konfigurasi Worker # package.json # Development (local dev server) wrangler dev # Deploy ke Cloudflare wrangler deploy
Worker Code Dasar
// src/index.ts
export interface Env {
// Environment bindings
MY_KV: KVNamespace;
MY_R2: R2Bucket;
MY_D1: D1Database;
MY_SECRET: string;
}
export default {
async fetch(request: Request, env: Env, ctx: ExecutionContext): Promise<Response> {
const url = new URL(request.url);
// Routing sederhana
switch (url.pathname) {
case '/':
return new Response('Hello from Cloudflare Workers! π', {
headers: { 'Content-Type': 'text/plain' }
});
case '/json':
return Response.json({
message: 'Ini response JSON',
timestamp: new Date().toISOString(),
colo: request.cf?.colo, // Edge location
country: request.cf?.country
});
case '/api/users':
return handleUsers(request, env);
default:
return new Response('Not Found', { status: 404 });
}
},
// Scheduled handler (Cron Triggers)
async scheduled(event: ScheduledEvent, env: Env, ctx: ExecutionContext): Promise<void> {
console.log('Cron triggered at:', new Date(event.scheduledTime));
await doPeriodicCleanup(env);
}
};
async function handleUsers(request: Request, env: Env): Promise<Response> {
if (request.method === 'GET') {
const users = await env.MY_D1.prepare('SELECT * FROM users').all();
return Response.json(users.results);
}
if (request.method === 'POST') {
const body = await request.json<{ name: string; email: string }>();
await env.MY_D1.prepare('INSERT INTO users (name, email) VALUES (?, ?)')
.bind(body.name, body.email)
.run();
return Response.json({ success: true }, { status: 201 });
}
return new Response('Method Not Allowed', { status: 405 });
}
Konfigurasi wrangler.toml
# wrangler.toml
name = "my-first-worker"
main = "src/index.ts"
compatibility_date = "2026-01-01"
# Environment variables
[vars]
ENVIRONMENT = "production"
API_URL = "https://api.example.com"
# KV Namespace binding
[[kv_namespaces]]
binding = "MY_KV"
id = "xxxxxxxxxxxxxxxxxxxx"
# R2 Bucket binding
[[r2_buckets]]
binding = "MY_R2"
bucket_name = "my-storage"
# D1 Database binding
[[d1_databases]]
binding = "MY_D1"
database_name = "my-db"
database_id = "xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx"
# Cron triggers
[triggers]
crons = ["*/5 * * * *"] # Every 5 minutes
# Custom domain
routes = [
{ pattern = "api.example.com/*", zone_name = "example.com" }
]
3. KV Storage
Cloudflare Workers KV adalah globally distributed key-value store yang dirancang untuk read-heavy workloads. Data direplikasi ke semua edge locations, sehingga read sangat cepat dari mana pun.
Karakteristik KV
| Aspek | Penjelasan |
|---|---|
| Consistency | π‘ Eventual consistency (write mungkin butuh waktu untuk propagate) |
| Read Latency | π’ Sub-millisecond dari edge terdekat |
| Max Value Size | 25 MB per value |
| Max Key Size | 512 bytes |
| TTL | Ya, per-key expiration |
| Metadata | Ya, custom metadata per key |
| Cocok Untuk | Config, session data, feature flags, caching |
| Tidak Cocok | Data yang butuh strong consistency, frequent writes |
Contoh Penggunaan KV
// CRUD Operations dengan KV
export default {
async fetch(request: Request, env: Env): Promise<Response> {
const url = new URL(request.url);
const key = url.searchParams.get('key');
// PUT β Set value
if (request.method === 'PUT') {
const body = await request.json();
await env.MY_KV.put(key, JSON.stringify(body), {
expirationTtl: 3600, // Expire dalam 1 jam
metadata: { type: 'user-data', version: 1 }
});
return Response.json({ success: true, key });
}
// GET β Get value
if (request.method === 'GET') {
const value = await env.MY_KV.get(key, { type: 'json' });
if (!value) {
return Response.json({ error: 'Not found' }, { status: 404 });
}
// Get with metadata
const { value: val, metadata } = await env.MY_KV.getWithMetadata(key, 'json');
return Response.json({ data: val, metadata });
}
// DELETE β Hapus value
if (request.method === 'DELETE') {
await env.MY_KV.delete(key);
return Response.json({ deleted: true });
}
// LIST β List keys dengan prefix
if (url.pathname === '/list') {
const prefix = url.searchParams.get('prefix') || '';
const keys = await env.MY_KV.list({ prefix, limit: 100 });
return Response.json({
keys: keys.keys.map(k => ({ name: k.name, metadata: k.metadata })),
list_complete: keys.list_complete,
cursor: keys.cursor
});
}
return new Response('Method Not Allowed', { status: 405 });
}
};
// Contoh: Feature flags dengan KV
async function getFeatureFlags(env: Env, userId: string): Promise<Record<string, boolean>> {
// Cek user-specific flags dulu
const userFlags = await env.MY_KV.get(`flags:${userId}`, 'json');
// Fallback ke global flags
const globalFlags = await env.MY_KV.get('flags:global', 'json');
return { ...globalFlags, ...userFlags };
}
4. Durable Objects
Durable Objects adalah Cloudflare's solution untuk stateful serverless computing. Berbeda dengan KV yang eventual consistency, Durable Objects menyediakan strong consistency dan single-threaded execution per object β cocok untuk use case yang membutuhkan koordinasi.
Kapan Menggunakan Durable Objects?
| Use Case | Penjelasan |
|---|---|
| Real-time Collab | Google Docs-like editing β semua client terhubung ke satu object |
| Chat Rooms | Setiap room adalah satu Durable Object β pesan dijamin urut |
| Game State | Game session β state konsisten untuk semua player |
| Rate Limiting | Counter per user yang strong consistent |
| Shopping Cart | Cart per user β tidak boleh hilang atau duplikat |
Contoh Durable Object β Chat Room
// src/chat-room.ts β Durable Object class
export class ChatRoom {
state: DurableObjectState;
sessions: WebSocket[] = [];
constructor(state: DurableObjectState, env: Env) {
this.state = state;
}
async fetch(request: Request): Promise<Response> {
// WebSocket upgrade untuk real-time chat
if (request.headers.get('Upgrade') === 'websocket') {
const pair = new WebSocketPair();
const [client, server] = Object.values(pair);
this.handleSession(server);
return new Response(null, {
status: 101,
webSocket: client
});
}
// REST API untuk ambil history
const messages = await this.state.storage.get('messages') || [];
return Response.json(messages);
}
handleSession(ws: WebSocket) {
ws.accept();
this.sessions.push(ws);
// Kirim history ke client baru
this.state.storage.get('messages').then(messages => {
ws.send(JSON.stringify({ type: 'history', messages: messages || [] }));
});
ws.addEventListener('message', async (event) => {
const msg = JSON.parse(event.data as string);
// Simpan pesan ke storage
const messages: any[] = await this.state.storage.get('messages') || [];
messages.push({
user: msg.user,
text: msg.text,
timestamp: Date.now()
});
await this.state.storage.put('messages', messages);
// Broadcast ke semua connected clients
const broadcast = JSON.stringify({
type: 'message',
user: msg.user,
text: msg.text,
timestamp: Date.now()
});
this.sessions.forEach(session => {
try { session.send(broadcast); } catch (e) { /* remove dead */ }
});
});
ws.addEventListener('close', () => {
this.sessions = this.sessions.filter(s => s !== ws);
});
}
}
// src/index.ts β Worker entry point
export default {
async fetch(request: Request, env: Env): Promise<Response> {
const url = new URL(request.url);
const roomId = url.searchParams.get('room') || 'default';
// Get Durable Object instance untuk room ini
const id = env.CHAT_ROOM.idFromName(roomId);
const obj = env.CHAT_ROOM.get(id);
return obj.fetch(request);
}
};
# wrangler.toml β Durable Object binding [[durable_objects.bindings]] name = "CHAT_ROOM" class_name = "ChatRoom" script_name = "chat-worker" [migrations] tag = "v1" new_classes = ["ChatRoom"]
5. R2 Object Storage
Cloudflare R2 adalah S3-compatible object storage yang tidak mengenakan egress fees. Ini adalah salah satu keunggulan terbesar R2 dibanding AWS S3 β Anda tidak perlu khawatir biaya bandwidth untuk download.
R2 vs AWS S3
| Aspek | R2 | AWS S3 |
|---|---|---|
| Egress Fee | π’ $0 (GRATIS!) | π΄ $0.09/GB |
| Storage | $0.015/GB/bulan | $0.023/GB/bulan |
| S3 Compatible | π’ Ya (S3 API) | π’ Ya (native) |
| Global Distribution | π’ Built-in | π‘ Per-region + CloudFront |
| Max Object Size | 5 TB | 5 TB |
| Tiered Pricing | π’ Sederhana | π‘ Kompleks (tier-based) |
Penggunaan R2
// Upload, download, dan manage objects di R2
export default {
async fetch(request: Request, env: Env): Promise<Response> {
const url = new URL(request.url);
const key = url.pathname.slice(1); // Remove leading /
// GET β Download file
if (request.method === 'GET') {
const object = await env.MY_R2.get(key);
if (!object) {
return new Response('File not found', { status: 404 });
}
const headers = new Headers();
object.writeHttpMetadata(headers);
headers.set('etag', object.httpEtag);
headers.set('content-type', object.httpMetadata?.contentType || 'application/octet-stream');
return new Response(object.body, { headers });
}
// PUT β Upload file
if (request.method === 'PUT') {
const contentType = request.headers.get('content-type') || 'application/octet-stream';
await env.MY_R2.put(key, request.body, {
httpMetadata: {
contentType,
contentDisposition: `inline; filename="${key}"`,
cacheControl: 'public, max-age=31536000'
},
customMetadata: {
uploadedAt: new Date().toISOString(),
originalName: key
}
});
return Response.json({ success: true, key });
}
// DELETE β Hapus file
if (request.method === 'DELETE') {
await env.MY_R2.delete(key);
return Response.json({ deleted: true });
}
return new Response('Method Not Allowed', { status: 405 });
}
};
R2 Public Access & Custom Domain
# Enable public access untuk bucket wrangler r2 bucket create my-storage # Set custom domain untuk public bucket # Di Cloudflare Dashboard > R2 > my-storage > Settings > Public Access # Atau gunakan Workers untuk serve dengan logic custom # Membuat presigned URL untuk temporary access # (Bisa digunakan dari client langsung) wrangler r2 object put my-storage/photo.jpg --file=./photo.jpg
6. D1 Database
Cloudflare D1 adalah serverless SQL database yang berjalan di edge. D1 menggunakan SQLite β database paling banyak digunakan di dunia β tetapi dikelola sepenuhnya oleh Cloudflare. D1 menyediakan strong consistency dan sangat cocok untuk aplikasi yang membutuhkan relational data.
Membuat dan Menggunakan D1
# Membuat D1 database
wrangler d1 create my-database
# Output:
# βββββββββββββββββββββββββββββββββ
# β database_id = "xxxx-xxxx-xxxx"β
# βββββββββββββββββββββββββββββββββ
# Tambahkan ke wrangler.toml
# Membuat schema
wrangler d1 execute my-database --command "
CREATE TABLE users (
id INTEGER PRIMARY KEY AUTOINCREMENT,
name TEXT NOT NULL,
email TEXT UNIQUE NOT NULL,
created_at DATETIME DEFAULT CURRENT_TIMESTAMP
);
CREATE INDEX idx_users_email ON users(email);
"
# Insert seed data
wrangler d1 execute my-database --command "
INSERT INTO users (name, email) VALUES
('Budi', 'budi@example.com'),
('Siti', 'siti@example.com'),
('Andi', 'andi@example.com');
"
# Execute dari file SQL
wrangler d1 execute my-database --file=./schema.sql
Query D1 dari Worker
// CRUD operations dengan D1
export default {
async fetch(request: Request, env: Env): Promise<Response> {
const url = new URL(request.url);
// GET all users
if (request.method === 'GET' && url.pathname === '/users') {
const { results } = await env.MY_D1
.prepare('SELECT * FROM users ORDER BY created_at DESC')
.all();
return Response.json(results);
}
// GET single user
if (request.method === 'GET' && url.pathname.startsWith('/users/')) {
const id = url.pathname.split('/')[2];
const user = await env.MY_D1
.prepare('SELECT * FROM users WHERE id = ?')
.bind(id)
.first();
if (!user) return Response.json({ error: 'Not found' }, { status: 404 });
return Response.json(user);
}
// POST create user
if (request.method === 'POST' && url.pathname === '/users') {
const { name, email } = await request.json<{ name: string; email: string }>();
try {
const result = await env.MY_D1
.prepare('INSERT INTO users (name, email) VALUES (?, ?) RETURNING *')
.bind(name, email)
.first();
return Response.json(result, { status: 201 });
} catch (e: any) {
if (e.message?.includes('UNIQUE')) {
return Response.json({ error: 'Email sudah terdaftar' }, { status: 409 });
}
throw e;
}
}
// Batch operations (lebih efisien)
if (url.pathname === '/users/batch') {
const batch = await env.MY_D1.batch([
env.MY_D1.prepare('SELECT COUNT(*) as total FROM users'),
env.MY_D1.prepare('SELECT * FROM users LIMIT 5'),
]);
return Response.json({ total: batch[0].results, recent: batch[1].results });
}
return new Response('Not Found', { status: 404 });
}
};
7. Queues
Cloudflare Queues adalah message queue yang memungkinkan Anda mengirim dan menerima pesan antara Workers. Ini berguna untuk async processing, batch operations, dan decoupling producers dari consumers.
Contoh Producer & Consumer
// Producer β mengirim pesan ke queue
export default {
async fetch(request: Request, env: Env): Promise<Response> {
if (request.method === 'POST' && request.url.endsWith('/submit')) {
const order = await request.json();
// Kirim ke queue untuk async processing
await env.ORDER_QUEUE.send({
orderId: order.id,
items: order.items,
total: order.total,
timestamp: Date.now()
});
return Response.json({
message: 'Order diterima dan sedang diproses',
orderId: order.id
});
}
return new Response('Send POST to /submit');
}
};
// Consumer β memproses pesan dari queue
export default {
async queue(batch: MessageBatch<OrderMessage>, env: Env): Promise<void> {
for (const message of batch.messages) {
try {
const order = message.body;
// Process order
await processPayment(order);
await updateInventory(order.items);
await sendConfirmation(order);
// Acknowledge message (berhasil diproses)
message.ack();
} catch (error) {
// Retry message (akan dikirim ulang)
message.retry();
console.error('Failed to process order:', error);
}
}
}
};
interface OrderMessage {
orderId: string;
items: Array<{ sku: string; qty: number }>;
total: number;
timestamp: number;
}
8. Cloudflare Pages
Cloudflare Pages adalah platform untuk deploy frontend applications (static sites dan full-stack apps) di Cloudflare network. Pages terintegrasi dengan Workers untuk backend functionality, mirip dengan Vercel atau Netlify.
Deploy dengan Pages
# Deploy dari CLI
npx wrangler pages project create my-site --production-branch main
# Deploy build output
wrangler pages deploy ./dist --project-name=my-site
# Dengan GitHub integration (rekomendasi):
# 1. Push code ke GitHub
# 2. Buka Cloudflare Dashboard > Pages > Create
# 3. Connect repository
# 4. Set build command: npm run build
# 5. Set output directory: dist (atau .next untuk Next.js)
# 6. Deploy otomatis setiap push
# Functions di Pages (server-side code)
# Buat file: functions/api/[[path]].ts
export const onRequest: PagesFunction<Env> = async (context) => {
const url = new URL(context.request.url);
if (url.pathname === '/api/hello') {
return Response.json({ message: 'Hello from Pages Functions!' });
}
// Akses KV, D1, dll dari Pages Functions
const data = await context.env.MY_KV.get('config', 'json');
return Response.json(data);
};
9. Best Practices
Performance
- Minimize bundle size β Workers punya limit 10MB (free) / 25MB (paid). Gunakan tree-shaking dan hindari dependency besar.
- Cache aggressively β Gunakan Cache API atau KV untuk meng-cache response yang sering diakses.
- Use Cache API β
caches.default.match()untuk edge caching built-in tanpa KV. - Minimize KV reads β KV eventual consistency β jangan gunakan untuk data yang harus konsisten. Gunakan D1 atau Durable Objects.
- Batch D1 queries β Gunakan
batch()untuk execute multiple queries dalam satu round-trip.
Architecture
- Use Workers for routing β Workers bisa bertindak sebagai API gateway, A/B testing router, atau auth middleware.
- Durable Objects untuk stateful β Gunakan untuk real-time collab, chat, game state β bukan KV.
- R2 untuk assets β Simpan images, videos, static files di R2 (no egress fee!).
- Queues untuk async β Decouple heavy processing dari request-response cycle.
Security
// Security headers middleware
export function addSecurityHeaders(response: Response): Response {
const headers = new Headers(response.headers);
headers.set('X-Content-Type-Options', 'nosniff');
headers.set('X-Frame-Options', 'DENY');
headers.set('X-XSS-Protection', '1; mode=block');
headers.set('Referrer-Policy', 'strict-origin-when-cross-origin');
headers.set('Permissions-Policy', 'camera=(), microphone=()');
headers.set('Content-Security-Policy', "default-src 'self'");
return new Response(response.body, {
status: response.status,
statusText: response.statusText,
headers
});
}
// CORS handling
function handleCORS(request: Request, response: Response): Response {
const origin = request.headers.get('Origin') || '*';
const headers = new Headers(response.headers);
headers.set('Access-Control-Allow-Origin', origin);
headers.set('Access-Control-Allow-Methods', 'GET, POST, PUT, DELETE, OPTIONS');
headers.set('Access-Control-Allow-Headers', 'Content-Type, Authorization');
headers.set('Access-Control-Max-Age', '86400');
return new Response(response.body, {
status: response.status,
headers
});
}