İlk kez
Cloudflare Workers
üzerinde bir JSON API yayına aldığımda, dizüstü bilgisayarımdan akşam 11'de deploy ettim ve çayımı bitirmeden
300'den fazla data center'da çalışıyordu. Dockerfile yok, Kubernetes cluster'ı yok, cold-start dramı yok. Tek bir
wrangler deploy ve 1,2 KB'lık bir bundle. O deneyim, Workers'ın JSON-in, JSON-out servisler için
benim varsayılanım olmasının sebebi — webhook'lar, proxy'ler, API agregatörleri, edge auth. Eğer backend'inin %80'i
"JSON parse et, bir şey yap, JSON döndür" ise, bu başlarken keşke elimde olsaydı dediğim makale.
Bir Cloudflare Worker esasen Cloudflare'in edge'inde V8 izolatları üzerinde çalışan tek bir JavaScript (veya TypeScript) fonksiyonudur. Bir Request alır, bir Response döndürür ve standart Fetch API'ye erişimi vardır. Eğer tarayıcıda fetch() kullandıysan, runtime'ın %90'ını zaten biliyorsun. Bilmediğin şey, oyuncak bir Worker'ı gerçekten production'da çalıştırabileceğin birinden ayıran küçük pattern setidir. Bu makale de işte bundan bahsediyor.
İlk JSON Endpoint'in
İşte kullanışlı en küçük JSON endpoint'i. Bir timestamp ve mesaj içeren tek bir object döndürür.
Bir Wrangler projesinde src/index.ts olarak kaydet:
export default {
async fetch(request, env, ctx) {
const payload = {
message: 'Hello from the edge',
servedAt: new Date().toISOString(),
colo: request.cf?.colo ?? 'unknown',
};
return Response.json(payload);
},
};Dikkat edilecek iki şey var. Birincisi, Response.json(), objeyi serialize eden ve senin için
Content-Type: application/json'ı ayarlayan statik bir helper. Özel bir content type'a ihtiyacın yoksa
kendi new Response(JSON.stringify(x))'ini yazma — er geç header'ı unutursun. İkincisi,
request.cf.colo sana hangi Cloudflare data center'ının isteği karşıladığını söyler.
Berlin'den bir istek FRA gösterir, Tokyo'dan NRT. Tüm "edge" vaadi tek bir alanda.
Bir JSON Request Body'sini Parse Etmek
POST endpoint'lerinin bir body okuması gerekir. Fetch API sana request.json() verir; body stream'ini okur ve tek bir çağrıda parse eder:
export default {
async fetch(request) {
if (request.method !== 'POST') {
return new Response('Method not allowed', { status: 405 });
}
const body = await request.json();
// body is now a regular JavaScript object
const { email, plan } = body;
return Response.json({
received: { email, plan },
ok: true,
});
},
};Temiz görünüyor ama bu kodda deploy ettikten sonra 24 saat içinde karşılaşacağın bir bug var:
eğer client boş bir body veya bozuk JSON gönderirse, request.json() bir
SyntaxError fırlatır, Worker'ın çöker ve Cloudflare jenerik bir 500 döndürür.
Müşterilerin önünde görmek isteyeceğin cevap bu değil.
Bozuk JSON ile Başa Çıkmak — 500 Olmasına İzin Verme
Body parsing'i her zaman bir try/catch ile sar ve düzgün bir 400 döndür. Her Worker'da kullandığım pattern bu:
async function readJson(request) {
try {
return { ok: true, data: await request.json() };
} catch (err) {
return {
ok: false,
error: 'Invalid JSON body',
detail: err.message,
};
}
}
export default {
async fetch(request) {
const result = await readJson(request);
if (!result.ok) {
return Response.json(result, { status: 400 });
}
const { email, plan } = result.data;
if (!email || !plan) {
return Response.json(
{ ok: false, error: 'email and plan are required' },
{ status: 422 },
);
}
return Response.json({ ok: true, email, plan });
},
};JSON API'ler için CORS
Worker'ın farklı bir origin üzerindeki bir tarayıcıdan çağrılacaksa — ki normal durum budur —
CORS
header'larına ihtiyacın var. Tarayıcılar, basit bir GET'ten daha fazlası için asıl istekten önce bir
OPTIONS preflight gönderir. Her ikisini de tek bir yerde hallet:
const CORS_HEADERS = {
'Access-Control-Allow-Origin': 'https://app.example.com',
'Access-Control-Allow-Methods': 'GET, POST, OPTIONS',
'Access-Control-Allow-Headers': 'Content-Type, Authorization',
'Access-Control-Max-Age': '86400',
};
export default {
async fetch(request) {
if (request.method === 'OPTIONS') {
return new Response(null, { status: 204, headers: CORS_HEADERS });
}
const data = { ping: 'pong', at: Date.now() };
return Response.json(data, { headers: CORS_HEADERS });
},
};Credential okuyan veya kullanıcı verisi döndüren hiçbir şeyde
Access-Control-Allow-Origin: *'dan kaçın. Dev'de zararsız görünüp prod'da güvenlik olayına
dönüşen kısayollardandır. Gerçekten hizmet ettiğin origin'leri sabit kodla veya env'deki bir
allow-list'ten oku.
Bir Upstream API'ye JSON Yönlendirmek
Bir Worker'ın en yaygın kullanımlarından biri ince bir proxy olarak: bir API key'i gizle, bir cevabı yeniden şekillendir, client'ın ihtiyaç duymadığı alanları çıkar veya iki upstream çağrısını birine birleştir. Aşağıda, bir upstream servisi çağıran, sadece ilgilendiğimiz alanları seçen ve daha temiz bir JSON payload'u döndüren bir Worker var:
export default {
async fetch(request, env) {
const url = new URL(request.url);
const userId = url.searchParams.get('id');
if (!userId) {
return Response.json({ error: 'id required' }, { status: 400 });
}
const upstream = await fetch(
`https://api.internal.example.com/users/${userId}`,
{
headers: { 'Authorization': `Bearer ${env.UPSTREAM_TOKEN}` },
},
);
if (!upstream.ok) {
return Response.json(
{ error: 'upstream failed', status: upstream.status },
{ status: 502 },
);
}
const full = await upstream.json();
// Strip internal fields before returning to the client
const safe = {
id: full.id,
displayName: full.display_name,
avatarUrl: full.avatar_url,
joinedAt: full.created_at,
};
return Response.json(safe);
},
};Dikkat edilmesi gereken iki şey. Birincisi, .json()'ı çağırmadan önce her zaman
upstream.ok'u kontrol et — upstream'den gelen bir 500'de HTML veya bir hata sayfası olur,
ve onun üzerinde .json() çağırmak başka herhangi bir bozuk JSON ile aynı şekilde fırlatır.
İkincisi, UPSTREAM_TOKEN gibi secret'ları Wrangler secret'larında tut
(wrangler secret put UPSTREAM_TOKEN) — asla wrangler.toml'da değil
ve asla git'e commit etme.
JSON Cevaplarını Edge'de Önbelleklemek
Bir upstream yavaş veya pahalı olduğunda, Cache API JSON'u edge'de memoize etmene izin verir. Her data center kendi cache'ini tutar, yani Frankfurt'taki ilk kullanıcı upstream maliyetini öder ve sonraki 10.000 kullanıcı bunu yakındaki RAM'den 5ms'den kısa sürede alır:
export default {
async fetch(request, env, ctx) {
const cache = caches.default;
const cacheKey = new Request(request.url, request);
let response = await cache.match(cacheKey);
if (response) {
return response;
}
const upstream = await fetch('https://api.example.com/popular-items');
const data = await upstream.json();
response = Response.json(data, {
headers: {
'Cache-Control': 'public, max-age=60',
},
});
// Don't block the response on the cache write
ctx.waitUntil(cache.put(cacheKey, response.clone()));
return response;
},
};ctx.waitUntil() bariz olmayan kısım. Onsuz cache.put() beklenir ve cevabın,
umursaması gerekmeyen bir disk yazımı için bekler. waitUntil sana cevabı hemen döndürme olanağı verir
ve runtime cache yazımını arka planda canlı tutar. Analitik beacon'ları, log forwarding, herhangi bir
fire-and-forget iş için kullanacağın aynı pattern'dir.
Wrangler ile Yerel Geliştirme
İterate etmek için Cloudflare hesabına ihtiyacın yok. Wrangler'ı kur, bir proje scaffold'la ve production'la yakından eşleşen yerel bir Workers runtime'ı elde et:
npm create cloudflare@latest my-json-api
cd my-json-api
npm run dev
# Worker is now live at http://localhost:8787
# Hit it from another terminal:
curl -X POST http://localhost:8787 \
-H "Content-Type: application/json" \
-d '{"email":"[email protected]","plan":"pro"}'Yerel runtime, Cloudflare'in production'da çalıştırdığı aynı motor olan workerd'ı kullanır.
Farklı olan davranışlar (KV gecikmesi, cache semantiği, request.cf alanları) iyi belgelenmiştir
ve basit JSON API'lerde seni nadiren ısırır. wrangler deploy ile deploy et ve aynı kod
saniyeler içinde globalde canlı olsun.
Worker JSON API'leri Kurmak için Faydalı Araçlar
JSON ile ilgilenen Worker'lar kurarken sürekli başvurduğum birkaç araç: tersine mühendislik yapmaya çalıştığım çirkin bir upstream cevabını güzelce yazdırmak için JSON Formatter, bir POST payload'u başarısız olduğunda nereyi tam olarak bilmem gerektiğinde JSON Validator, alan-seçme mantığını yazmadan önce planlamak için JSON Path ve belirli bir endpoint için kablo boyutunun gerçekten önemli olup olmadığını kontrol etmek istediğimde JSON Minifier.
JSON formatının kendisi
RFC 8259'da
belirtilmiştir — "parser'ım NaN'a izin veriyor mu?" gibi bir edge case'e çarparsan şöyle bir
göz atmaya değer (cevap: vermemeli). Cloudflare'in kendi
Workers örnek galerisinde
bu makaledeki temelleri aştıktan sonra JWT doğrulama, A/B testi, HTML yeniden yazma ve bir düzine başka pattern için
tarifler var.
Toparlayalım
Cloudflare Workers, JSON API'ler için çok uygun bir seçimdir — küçük, hızlı, globalde
dağıtılmış ve yan projeleri çalışır bırakacak kadar ucuz. Mutlu yol sadece request.json() ve
Response.json()'dır, ancak production yolu dört ekstra alışkanlığı içerir: body parsing'i
try/catch ile sar, CORS header'larını kasıtlı olarak ekle, proxy'lenmiş cevapları parse etmeden önce
upstream.ok'u kontrol et ve cache yazımları ile diğer arka plan işleri için
ctx.waitUntil kullan. Bu dördünü doğru yap ve ayakta kalan Worker'lar deploy edeceksin.