İ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:

js
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:

js
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:

js
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 });
  },
};
Debug ipucu: Bir client "API'niz bozuk" derken Worker 400 gösteriyorsa, onda dokuz ihtimalle onların JSON'ı bozuktur — sonda virgül, tırnaksız anahtar veya başında kazara bir BOM karakteri. Payload'ı JSON Validator'a yapıştırmalarını iste, sorun genellikle bir dakikadan kısa sürede ortaya çıkar.

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:

js
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:

js
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:

js
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:

bash
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.