Membangun Aplikasi Serverless dengan Cloudflare Workers: Tutorial Step-by-Step
Pendahuluan
Bayangkan kamu ingin membangun layanan backend seperti Gojek atau Tokopedia — sistem yang harus bisa menangani jutaan request setiap hari tanpa downtime. Salah satu pendekatan modern yang semakin populer adalah arsitektur serverless, di mana kamu tidak perlu mengelola server secara langsung.
Cloudflare Workers adalah platform serverless yang berjalan di edge network Cloudflare — tersebar di lebih dari 300 lokasi di seluruh dunia. Artinya, kode kamu dieksekusi di lokasi yang paling dekat dengan pengguna, menghasilkan latensi yang sangat rendah.
Yang membuat Workers menarik:
- Gratis untuk memulai — 100.000 request/hari di tier gratis
- Cold start nyaris nol — tidak seperti AWS Lambda yang bisa delay beberapa detik
- Runtime JavaScript/TypeScript berbasis V8 isolates, bukan Node.js penuh
- Deploy global otomatis — satu perintah, langsung tersebar ke seluruh dunia
Di tutorial ini, kita akan membangun API serverless dari nol, mulai dari setup hingga deploy dan debugging.
Persiapan Lingkungan dan Inisialisasi Proyek
Prasyarat
Sebelum mulai, pastikan sudah terinstall:
- Node.js versi 18 ke atas
- npm atau pnpm
- Akun Cloudflare (gratis di cloudflare.com)
Instalasi Wrangler CLI
Wrangler adalah CLI resmi Cloudflare untuk mengelola Workers.
npm install -g wrangler
# Verifikasi instalasi
wrangler --version
Login ke Cloudflare
wrangler login
Perintah ini akan membuka browser dan meminta kamu untuk authorize Wrangler mengakses akun Cloudflare-mu.
Membuat Proyek Baru
# Buat proyek Workers baru
wrangler init my-api-worker
# Masuk ke direktori proyek
cd my-api-worker
Wrangler akan menanyakan beberapa opsi. Pilih:
- TypeScript: Ya (sangat direkomendasikan)
- Deploy: Tidak dulu (kita akan deploy manual)
Struktur folder yang dihasilkan:
my-api-worker/
├── src/
│ └── index.ts ← kode utama Worker
├── wrangler.toml ← konfigurasi project
├── package.json
└── tsconfig.json
Konfigurasi wrangler.toml
name = "my-api-worker"
main = "src/index.ts"
compatibility_date = "2024-01-01"
[vars]
ENVIRONMENT = "production"
Menulis Kode Worker: Menangani Request dan Response
Cloudflare Workers menggunakan Fetch API standar web. Jika kamu sudah familiar dengan Menguasai Fetch API dan Async/Await di JavaScript, konsep ini akan terasa sangat familiar.
Struktur Dasar Worker
// src/index.ts
export interface Env {
ENVIRONMENT: string;
}
export default {
async fetch(
request: Request,
env: Env,
ctx: ExecutionContext
): Promise<Response> {
return new Response("Hello dari Cloudflare Workers!", {
status: 200,
headers: {
"Content-Type": "text/plain",
},
});
},
};
Router Sederhana Berdasarkan Path dan Method
Mari buat router yang lebih realistis untuk REST API:
// src/index.ts
export interface Env {
ENVIRONMENT: string;
}
function jsonResponse(data: unknown, status = 200): Response {
return new Response(JSON.stringify(data), {
status,
headers: {
"Content-Type": "application/json",
"Access-Control-Allow-Origin": "*",
},
});
}
export default {
async fetch(
request: Request,
env: Env,
ctx: ExecutionContext
): Promise<Response> {
const url = new URL(request.url);
const { pathname } = url;
const method = request.method;
// Handle CORS preflight
if (method === "OPTIONS") {
return new Response(null, {
headers: {
"Access-Control-Allow-Origin": "*",
"Access-Control-Allow-Methods": "GET, POST, OPTIONS",
"Access-Control-Allow-Headers": "Content-Type",
},
});
}
// Routing
if (pathname === "/" && method === "GET") {
return jsonResponse({
message: "API berjalan dengan baik!",
environment: env.ENVIRONMENT,
timestamp: new Date().toISOString(),
});
}
if (pathname === "/users" && method === "GET") {
const users = [
{ id: 1, nama: "Budi Santoso", kota: "Jakarta" },
{ id: 2, nama: "Siti Rahayu", kota: "Surabaya" },
{ id: 3, nama: "Ahmad Fauzi", kota: "Bandung" },
];
return jsonResponse({ data: users, total: users.length });
}
if (pathname === "/echo" && method === "POST") {
const body = await request.json();
return jsonResponse({
echo: body,
receivedAt: new Date().toISOString(),
});
}
// 404 untuk route yang tidak ditemukan
return jsonResponse({ error: "Route tidak ditemukan" }, 404);
},
};
Membaca Query Parameters
// Contoh: /search?q=javascript&limit=10
if (pathname === "/search" && method === "GET") {
const query = url.searchParams.get("q") ?? "";
const limit = parseInt(url.searchParams.get("limit") ?? "10");
return jsonResponse({
query,
limit,
results: [`Hasil untuk "${query}" — ${limit} item`],
});
}
Deploy, Testing, dan Debugging
Menjalankan Development Server
wrangler dev
Worker kamu akan berjalan di http://localhost:8787. Setiap perubahan kode langsung hot-reload.
Testing dengan curl
# Test endpoint utama
curl http://localhost:8787/
# Test endpoint users
curl http://localhost:8787/users
# Test POST echo
curl -X POST http://localhost:8787/echo \
-H "Content-Type: application/json" \
-d '{"pesan": "Halo dari lokal!"}'
# Test query params
curl "http://localhost:8787/search?q=cloudflare&limit=5"
Deploy ke Production
wrangler deploy
Output sukses:
✓ Uploaded my-api-worker (1.23 sec)
✓ Deployed my-api-worker triggers (0.45 sec)
https://my-api-worker.username.workers.dev
Melihat Logs Real-time
# Stream logs dari production
wrangler tail
# Filter berdasarkan status
wrangler tail --status error
Contoh Kasus Nyata: Membuat API Proxy Sederhana
Salah satu use case paling umum Workers adalah sebagai API proxy — menambahkan layer autentikasi, rate limiting, atau transformasi data di depan API eksternal. Konsep ini mirip dengan Apa itu Adapter Pattern? yang membungkus interface yang tidak kompatibel.
// src/index.ts — API Proxy dengan validasi API Key
export interface Env {
API_KEY: string; // Secret dari Cloudflare dashboard
UPSTREAM_API: string; // URL API yang diproxy
}
async function validateApiKey(request: Request, env: Env): Promise<boolean> {
const authHeader = request.headers.get("Authorization");
if (!authHeader) return false;
const [scheme, key] = authHeader.split(" ");
return scheme === "Bearer" && key === env.API_KEY;
}
export default {
async fetch(
request: Request,
env: Env,
ctx: ExecutionContext
): Promise<Response> {
// Validasi API key
const isValid = await validateApiKey(request, env);
if (!isValid) {
return new Response(
JSON.stringify({ error: "Unauthorized — API key tidak valid" }),
{
status: 401,
headers: { "Content-Type": "application/json" },
}
);
}
// Forward request ke upstream API
const url = new URL(request.url);
const upstreamUrl = `${env.UPSTREAM_API}${url.pathname}${url.search}`;
const upstreamRequest = new Request(upstreamUrl, {
method: request.method,
headers: {
"Content-Type": "application/json",
"User-Agent": "KamusNgoding-Proxy/1.0",
},
body: request.method !== "GET" ? request.body : null,
});
try {
const response = await fetch(upstreamRequest);
const data = await response.json();
// Tambahkan metadata ke response
return new Response(
JSON.stringify({
data,
meta: {
proxiedAt: new Date().toISOString(),
upstreamStatus: response.status,
},
}),
{
status: response.status,
headers: {
"Content-Type": "application/json",
"X-Proxied-By": "Cloudflare-Workers",
},
}
);
} catch (error) {
return new Response(
JSON.stringify({ error: "Gagal menghubungi upstream API" }),
{ status: 502, headers: { "Content-Type": "application/json" } }
);
}
},
};
Menambahkan Secret via Wrangler
# Tambahkan API key sebagai secret (tidak masuk ke kode)
wrangler secret put API_KEY
# Prompt akan muncul untuk memasukkan nilainya
wrangler secret put UPSTREAM_API
Deploy ulang setelah menambahkan secret
wrangler deploy
Troubleshooting: Error yang Sering Muncul
Error: Script startup exceeded CPU time limit
Penyebab: Kode inisialisasi di luar handler fetch() memakan terlalu banyak CPU, misalnya parsing data besar saat module load.
Solusi:
// ❌ Jangan lakukan ini — dieksekusi saat startup
const heavyData = JSON.parse(veryLargeJsonString);
// ✅ Lakukan di dalam handler — hanya saat diperlukan
export default {
async fetch(request: Request): Promise<Response> {
const heavyData = JSON.parse(veryLargeJsonString); // lazy load
return new Response("OK");
},
};
TypeError: Cannot read properties of undefined saat akses env
Penyebab: Variable environment (env.VARIABLE_NAME) tidak terdefinisi di wrangler.toml atau belum ditambahkan sebagai secret.
Solusi:
# wrangler.toml — tambahkan definisi vars
[vars]
ENVIRONMENT = "development"
# Untuk secrets, jalankan:
# wrangler secret put NAMA_SECRET
// Selalu validasi env sebelum digunakan
export default {
async fetch(request: Request, env: Env): Promise<Response> {
if (!env.API_KEY) {
return new Response("Konfigurasi server tidak lengkap", { status: 500 });
}
// ...
},
};
Error 1101: Worker threw an unhandled exception
Penyebab: Exception tidak tertangkap di dalam handler, biasanya dari await fetch() ke URL yang tidak valid atau timeout.
Solusi:
export default {
async fetch(request: Request, env: Env): Promise<Response> {
try {
const response = await fetch("https://api.eksternal.com/data");
if (!response.ok) {
throw new Error(`Upstream error: ${response.status}`);
}
const data = await response.json();
return Response.json(data);
} catch (error) {
// Log error untuk debugging
console.error("Error:", error);
return new Response(
JSON.stringify({
error: "Internal server error",
message: error instanceof Error ? error.message : "Unknown error",
}),
{ status: 500, headers: { "Content-Type": "application/json" } }
);
}
},
};
CORS Error di Browser
Penyebab: Response dari Worker tidak menyertakan header Access-Control-Allow-Origin, sehingga browser memblokir request dari domain berbeda.
Solusi:
// Helper function untuk response dengan CORS headers
function corsResponse(body: string, status = 200): Response {
return new Response(body, {
status,
headers: {
"Content-Type": "application/json",
"Access-Control-Allow-Origin": "*", // atau domain spesifik
"Access-Control-Allow-Methods": "GET, POST, PUT, DELETE, OPTIONS",
"Access-Control-Allow-Headers": "Content-Type, Authorization",
},
});
}
// Jangan lupa handle OPTIONS preflight
if (request.method === "OPTIONS") {
return corsResponse("", 204);
}
Pertanyaan yang Sering Diajukan (FAQ)
Apa perbedaan Cloudflare Workers dengan AWS Lambda?
Cloudflare Workers berjalan di V8 isolates yang jauh lebih ringan daripada container Lambda — cold start Workers kurang dari 1ms, sedangkan Lambda bisa mencapai ratusan milidetik. Namun, Workers memiliki batasan lebih ketat: tidak ada akses sistem file, runtime bukan Node.js penuh, dan batas CPU time 10-50ms per request di tier gratis.
Apakah Cloudflare Workers bisa digunakan untuk aplikasi production besar?
Ya, sangat bisa. Workers cocok untuk API stateless, proxy, transformasi data, dan logika edge. Jika kamu ingin membangun layanan seperti Tokopedia untuk menangani jutaan request harian, Workers bisa menjadi layer API yang sangat efisien. Untuk data persisten, gunakan Cloudflare KV, D1 (SQLite), atau R2 (object storage).
Bagaimana cara menyimpan data di Cloudflare Workers?
Workers tidak memiliki filesystem, tapi Cloudflare menyediakan beberapa opsi storage:
- KV (Key-Value Store) — cocok untuk data yang sering dibaca, eventual consistency
- D1 — database SQLite serverless, cocok untuk data relasional
- R2 — object storage seperti AWS S3, untuk file besar
Apakah TypeScript wajib digunakan?
Tidak wajib, tapi sangat direkomendasikan. TypeScript memberikan type safety yang membantu menghindari bug runtime, terutama saat mendefinisikan interface Env untuk environment variables. Jika sudah memahami Pengenalan TypeScript: Superset JavaScript yang Lebih Aman, transisi ke Workers TypeScript akan terasa mulus.
Berapa biaya Cloudflare Workers?
Tier gratis mencakup 100.000 request/hari dan batas CPU 10ms per request. Tier paid ($5/bulan) memberikan 10 juta request/bulan, batas CPU hingga 50ms, dan akses ke fitur seperti Cron Triggers dan Durable Objects. Untuk proyek awal atau traffic sedang, tier gratis sudah lebih dari cukup.
Kesimpulan
Kita telah membangun aplikasi serverless lengkap menggunakan Cloudflare Workers — mulai dari setup environment dengan Wrangler, menulis REST API dengan routing dan CORS handling, hingga membangun API proxy dengan autentikasi. Cloudflare Workers memberikan cara yang sangat efisien dan hemat biaya untuk menjalankan logika backend tanpa pusing mengelola infrastruktur server.
Langkah selanjutnya yang bisa kamu eksplorasi adalah integrasi Workers dengan Cloudflare KV untuk caching data, D1 untuk database, atau menggunakan Cron Triggers untuk menjalankan task terjadwal. Kamu juga bisa mengotomatisasi proses deploy menggunakan Tutorial GitHub Actions untuk Pemula agar setiap push ke repository langsung trigger deploy ke Cloudflare.
Selamat belajar dan terus bereksperimen! Jika ada pertanyaan atau kamu menemukan kasus unik saat membangun dengan Workers, jangan ragu untuk mengeksplorasi artikel-artikel lain di KamusNgoding — komunitas developer Indonesia semakin kuat ketika kita saling berbagi ilmu.