---
title: 'HTTP Üzerinden GraphQL'
source: 'https://academia.sh/tr/kurslar/api-tasarimi/http-uzerinden-graphql'
course: 'Web API Tasarımı'
language: tr
updated: '2026-08-17T18:06:45+00:00'
license: 'CC BY-SA 4.0'
---

# HTTP Üzerinden GraphQL

Tek uç nokta üzerinden aktarım, POST ile GET arasındaki önbellekleme farkı, önceden kaydedilmiş belgeler ve tek adresin aracılara ve kütüklere bıraktığı bilginin ölçülmesi.

Buraya kadar yazılan her şey tek bir süreç içinde çalıştı: sorgu bir dizgiydi, sonuç bir
nesne. Gerçek bir servis sorguyu ağ üzerinden alır ve yanıtı ağ üzerinden döndürür.

Bu yalnız bir sarmalama işi değildir. Tek uç nokta ve çoğunlukla tek yöntem kullanan bir
aktarım, önceki konuda kurulan her şeyi yeniden düşünmeyi gerektirir: adres tabanlı
önbellekleme, durum kodu eşlemesi, aracıların ve kütüklerin gördüğü bilgi. Bu ders
çalıştırıcıyı HTTP'ye oturtur ve bu kararın ne götürüp ne getirdiğini ölçer.

## Sunucu

Sunucu üç aktarım biçimini destekler. `POST` gövdesinde sorguyu, değişkenleri ve işlem
adını taşır. `GET` aynı üçlüyü sorgu parametrelerinde taşır. Üçüncüsü, sorgu metni yerine
**önceden kaydedilmiş belgenin kimliğini** taşır.

Önceki derslerde yazılan ayrıştırıcı, denetleyici, şema, maliyet ölçeri ve çalıştırıcı
olduğu gibi kullanılır; sunucu bunları sırayla çağıran ince bir katmandır.

```js
// sunucu.mjs — GraphQL'i HTTP uzerine oturtur
//   POST /graphql            govdede { query, variables, operationName }
//   GET  /graphql?query=...  sorgu adreste; onbelleklenebilir
//   GET  /graphql?kimlik=... onceden kaydedilmis belge; kisa ve izin listesiyle sinirli
import { createServer } from "node:http";
import { createHash } from "node:crypto";
import { ayristir } from "./ayristirici.mjs";
import { denetle } from "./denetle.mjs";
import { calistir } from "./calistirici.mjs";
import { olc, sinirDenetle } from "./maliyet.mjs";
import { SEMA_L as SEMA } from "./sema-liste.mjs";
import { COZUCULER } from "./veri.mjs";

const SINIR = { enCokDerinlik: 6, enCokMaliyet: 1000 };
export const kimlikYaz = (metin) => createHash("sha256").update(metin).digest("hex").slice(0, 12);

// Onceden kaydedilmis belgeler: istemci yalniz kimligini gonderir.
export const KAYITLI = new Map([
  `query OduncListesi { oduncler(ilk: 6) { id durum } }`,
  `query OduncAyrinti($id: ID!) { odunc(id: $id) { id durum uye { ad } } }`,
].map((m) => [kimlikYaz(m), m]));

const govdeOku = (istek) =>
  new Promise((coz) => { let v = ""; istek.on("data", (p) => (v += p)); istek.on("end", () => coz(v)); });

const yaz = (yanit, kod, nesne, ekBaslik = {}) => {
  yanit.writeHead(kod, { "content-type": "application/graphql-response+json; charset=utf-8", ...ekBaslik });
  yanit.end(JSON.stringify(nesne));
};

createServer(async (istek, yanit) => {
  yanit.sendDate = false;
  const adres = new URL(istek.url, "http://yerel");
  if (adres.pathname !== "/graphql") return yaz(yanit, 404, { errors: [{ ileti: "yol yok", kod: "yol_yok" }] });

  let metin, degiskenler = {}, islemAdi;
  if (istek.method === "POST") {
    const g = JSON.parse((await govdeOku(istek)) || "{}");
    ({ query: metin, variables: degiskenler = {}, operationName: islemAdi } = g);
  } else {
    const kimlik = adres.searchParams.get("kimlik");
    metin = kimlik ? KAYITLI.get(kimlik) : adres.searchParams.get("query");
    degiskenler = JSON.parse(adres.searchParams.get("variables") ?? "{}");
    islemAdi = adres.searchParams.get("operationName") ?? undefined;
    if (kimlik && !metin) return yaz(yanit, 404, { errors: [{ ileti: "kayıtlı belge yok", kod: "belge_yok" }] });
  }

  let belge;
  try { belge = ayristir(metin ?? ""); }
  catch (h) { return yaz(yanit, 400, { errors: [{ ileti: h.message, kod: "ayristirma" }] }); }

  const denetimHatalari = denetle(SEMA, belge);
  if (denetimHatalari.length) return yaz(yanit, 400, { errors: denetimHatalari.map((i) => ({ ileti: i, kod: "denetim" })) });

  const islem = islemAdi ? belge.islemler.find((i) => i.ad === islemAdi) : belge.islemler[0];
  const olcum = olc(SEMA, belge, islem, { varsayilanCarpan: 6, degiskenler });
  const asim = sinirDenetle(olcum, SINIR);
  if (asim.length) return yaz(yanit, 400, { errors: asim.map((i) => ({ ileti: i, kod: "sinir" })) });

  const sonuc = await calistir(SEMA, COZUCULER, belge, { islemAdi, degiskenler });
  // Alan duzeyi hata kismi basaridir: istek yerine getirilmistir, 200 doner.
  const baslik = istek.method === "GET" ? { "cache-control": "public, max-age=30" } : { "cache-control": "no-store" };
  yaz(yanit, 200, sonuc, { ...baslik, "graphql-islem": islem.ad ?? "adsiz" });
}).listen(8441, "127.0.0.1", () => {
  console.log("graphql sunucusu 127.0.0.1:8441");
  for (const [k, m] of KAYITLI) console.log(`kayitli ${k} ${/query (\w+)/.exec(m)[1]}`);
});
```

```bash
#!/usr/bin/env bash
# Uc aktarim bicimi ve iki hata durumu
node sunucu.mjs > /tmp/baslangic & s=$!
sleep 0.6
cat /tmp/baslangic
KIMLIK=$(grep 'OduncAyrinti' /tmp/baslangic | cut -d' ' -f2)

goster() { printf -- '--- %s ---\n' "$1"; shift; curl -sS -D - -o /tmp/g "$@" | grep -iE '^HTTP|^cache-control|^graphql-islem' | tr -d '\r'; cat /tmp/g; echo; }

goster "POST (gövdede sorgu)" -X POST -H 'content-type: application/json' \
  -d '{"query":"query OduncAyrinti($id: ID!) { odunc(id: $id) { id durum uye { ad } } }","variables":{"id":"O-2"}}' \
  http://127.0.0.1:8441/graphql

goster "GET (adreste sorgu)" --get --data-urlencode 'query=query OduncListesi { oduncler(ilk: 2) { id durum } }' \
  http://127.0.0.1:8441/graphql

goster "GET (kayıtlı belge)" "http://127.0.0.1:8441/graphql?kimlik=$KIMLIK&variables=%7B%22id%22%3A%22O-1%22%7D"

goster "şemada olmayan alan" -X POST -H 'content-type: application/json' \
  -d '{"query":"{ odunc(id: \"O-1\") { gecikmeCezasi } }"}' http://127.0.0.1:8441/graphql

goster "sınır aşımı" -X POST -H 'content-type: application/json' \
  -d '{"query":"{ oduncler(ilk: 500) { id uye { ad oduncler(ilk: 500) { id } } } }"}' http://127.0.0.1:8441/graphql

kill "$s"; wait "$s" 2>/dev/null
```

```
graphql sunucusu 127.0.0.1:8441
kayitli e51e709a4e07 OduncListesi
kayitli dcb9c7bb85fa OduncAyrinti
--- POST (gövdede sorgu) ---
HTTP/1.1 200 OK
cache-control: no-store
graphql-islem: OduncAyrinti
{"data":{"odunc":{"id":"O-2","durum":"ACIK","uye":{"ad":"Üye 1"}}}}
--- GET (adreste sorgu) ---
HTTP/1.1 200 OK
cache-control: public, max-age=30
graphql-islem: OduncListesi
{"data":{"oduncler":[{"id":"O-1","durum":"ACIK"},{"id":"O-2","durum":"ACIK"}]}}
--- GET (kayıtlı belge) ---
HTTP/1.1 200 OK
cache-control: public, max-age=30
graphql-islem: OduncAyrinti
{"data":{"odunc":{"id":"O-1","durum":"ACIK","uye":{"ad":"Üye 0"}}}}
--- şemada olmayan alan ---
HTTP/1.1 400 Bad Request
{"errors":[{"ileti":"Sorgu.odunc: Odunc tipinde \"gecikmeCezasi\" alanı yok","kod":"denetim"}]}
--- sınır aşımı ---
HTTP/1.1 400 Bad Request
{"errors":[{"ileti":"maliyet 502000, sınır 1000","kod":"sinir"}]}
```

Belge kimlikleri karma işlevinden türetildiği için makineden makineye değişmez, ama belge
metni değiştiğinde kimlik de değişir.

## Durum Kodunun Anlamı

Yanıtların durum kodları iki gruba ayrılıyor ve ayrım keyfî değildir.

**400 dönen iki durum**, sorgunun hiç çalıştırılmadığı durumlardır: belge ayrıştırılamamış,
şemaya uymuyor ya da sınırı aşıyor. İstek yerine getirilmemiştir; istemcinin **gönderdiği
şey** hatalıdır ve yeniden denemek işe yaramaz.

**200 dönen durumlar**, sorgunun çalıştırıldığı durumlardır — alan düzeyinde hata bulunsa
bile. Önceki derste kurulan kısmi başarı budur: bir alanın çökmesi isteğin yerine
getirilmediği anlamına gelmez, çünkü yanıtın geri kalanı geçerlidir. Yanıtta hem `data` hem
`errors` bulunabilir ve durum kodu bunu bildiremez.

Ayrım, önceki konudaki 400 ile 422 ayrımının aynısıdır: anlaşılamayan istek ile anlaşılıp
kısmen karşılanan istek farklı şeylerdir. Buradaki fark, ikinci grubun başarısız
sayılmamasıdır.

## Aracıların ve Kütüklerin Gördüğü

Tek uç nokta kararının asıl bedeli, isteğin ayırt edici bilgisinin adresten gövdeye
taşınmasıdır. Yol üzerindeki bir önbellek ya da erişim kütüğü isteği yalnız yöntem ve
adresten tanır.

```js
// aracinin-gordugu.mjs — yol uzerindeki bir araci istegi yalnizca yontem+adresten tanir.
// Ayni is yuku dort aktarim bicimiyle yollanirsa aracinin gordugu ne kadar degisir?
import { createHash } from "node:crypto";
const kimlikYaz = (m) => createHash("sha256").update(m).digest("hex").slice(0, 12);

// Yarim gunluk is yuku: dort ayri islem, tekrarli
const ISLEMLER = [
  { ad: "OduncListesi", yol: "/oduncler", sorgu: `query OduncListesi { oduncler(ilk: 6) { id durum } }`, degisken: {} },
  { ad: "OduncAyrinti", yol: "/odunc/O-1", sorgu: `query OduncAyrinti($id: ID!) { odunc(id: $id) { id durum uye { ad } } }`, degisken: { id: "O-1" } },
  { ad: "OduncAyrinti", yol: "/odunc/O-2", sorgu: `query OduncAyrinti($id: ID!) { odunc(id: $id) { id durum uye { ad } } }`, degisken: { id: "O-2" } },
  { ad: "UyeOzeti", yol: "/uyeler/U-1001", sorgu: `query UyeOzeti($k: ID!) { uye(kimlik: $k) { ad ceza } }`, degisken: { k: "U-1001" } },
];
const YUK = Array.from({ length: 12 }, (_, i) => ISLEMLER[i % ISLEMLER.length]);

const enc = encodeURIComponent;
const BICEMLER = {
  "kaynak temelli (GET)":   (o) => ["GET", o.yol],
  "GraphQL POST":           () => ["POST", "/graphql"],
  "GraphQL GET (sorgu)":    (o) => ["GET", `/graphql?query=${enc(o.sorgu)}&variables=${enc(JSON.stringify(o.degisken))}`],
  "GraphQL GET (kayıtlı)":  (o) => ["GET", `/graphql?kimlik=${kimlikYaz(o.sorgu)}&variables=${enc(JSON.stringify(o.degisken))}`],
};

console.log("aktarım                 istek  önbellek anahtarı  önbellekten  ayırt edilen işlem  ort. adres");
for (const [ad, uret] of Object.entries(BICEMLER)) {
  const anahtarlar = YUK.map((o) => uret(o).join(" "));
  const tekil = new Set(anahtarlar);
  // Araci yalniz GET isteklerini onbellekleyebilir.
  const onbelleklenebilir = anahtarlar[0].startsWith("GET") ? YUK.length - tekil.size : 0;
  const islemAyrimi = new Set(anahtarlar.map((a) => a.split("?")[0] + (a.includes("query=") || a.includes("kimlik=") ? a.split("&")[0].split("?")[1] : "")));
  const ortUzunluk = Math.round(anahtarlar.reduce((t, a) => t + a.length, 0) / anahtarlar.length);
  console.log(`${ad.padEnd(23)} ${String(YUK.length).padStart(5)}  ${String(tekil.size).padStart(17)}  ${String(onbelleklenebilir).padStart(11)}  ${String(islemAyrimi.size).padStart(18)}  ${String(ortUzunluk).padStart(10)}`);
}
```

```
aktarım                 istek  önbellek anahtarı  önbellekten  ayırt edilen işlem  ort. adres
kaynak temelli (GET)       12                  4            8                   4          15
GraphQL POST               12                  1            0                   1          13
GraphQL GET (sorgu)        12                  4            8                   3         154
GraphQL GET (kayıtlı)      12                  4            8                   3          65
```

`POST` satırı tek uç noktanın bedelini gösteriyor: on iki isteğin tamamı tek bir önbellek
anahtarına düşüyor, hiçbiri önbellekten karşılanamıyor ve erişim kütüğü on iki isteği tek
bir satır türü olarak görüyor. Bir aracı açısından bütün istekler aynıdır.

`GET` satırları bunu geri kazanıyor. Sorgu adrese taşındığında önbellek anahtarı sayısı
kaynak temelli aktarımla eşitleniyor ve on iki isteğin sekizi önbellekten karşılanabilir
hâle geliyor. Bedeli adres uzunluğudur: ortalama 154 karakter, ve gerçek sorgular bundan
çok daha uzundur. Kayıtlı belgeler bu bedeli 65 karaktere indiriyor.

Ayırt edilen işlem sütunundaki 4'e karşı 3 farkı bir eksiklik değil, farklı bir gruplamadır.
Kaynak temelli kütük istekleri **kaynağa** göre ayırır; GraphQL kütüğü **işleme** göre.
`OduncAyrinti` iki farklı kimlikle çağrıldığında ikinci gruplamada tek satırdır. Hangisinin
işe yaradığı sorulan soruya bağlıdır: "hangi kaynak çok okunuyor" sorusu birinciyi, "hangi
ekran yavaş" sorusu ikinciyi ister.

Kayıtlı belgelerin ikinci bir sonucu daha vardır. Sunucu yalnız kaydettiği belgeleri
çalıştırdığı için, istemcinin gönderdiği keyfî bir sorgu hiç ayrıştırılmaz. Maliyet
sınırının koruduğu şeyin bir bölümü böylece kendiliğinden korunur: bilinmeyen bir belge
çalıştırılmadan reddedilir.

## Özet

- GraphQL isteği üç biçimde taşınabilir: gövdede sorgu ile POST, adreste sorgu ile GET ve
  önceden kaydedilmiş belge kimliği ile GET.
- Çalıştırılmayan istekler (ayrıştırma, denetim, sınır) 400 döner; çalıştırılan istekler
  alan düzeyinde hata içerse bile 200 döner, çünkü kısmi başarı başarısızlık değildir.
- Tek uç noktaya yapılan POST istekleri tek bir önbellek anahtarına düşer; on iki isteğin
  hiçbiri önbellekten karşılanamaz ve kütükte hepsi tek satır türü olarak görünür.
- Sorgunun adrese taşınması önbellek anahtarı sayısını kaynak temelli aktarımla eşitler;
  bedeli 154 karaktere çıkan adres uzunluğudur.
- Kayıtlı belgeler adresi 65 karaktere indirir ve sunucunun yalnız izin verilen belgeleri
  çalıştırmasını sağlar.
- Kaynak temelli kütük kaynağa, GraphQL kütüğü işleme göre gruplar; ikisi farklı sorulara
  yanıt verir.

## Kurs Kapanışı

Bu kurs bir arayüzün tasarlanmasını dört aşamada ele aldı ve her aşamada aynı ölçütü
kullandı: bir kararın karşılığı ölçülebiliyor mu?

**API biçemleri** kaynak temelli, uzak yordam çağrısı temelli ve sorgu temelli yaklaşımları
karşılaştırdı. Aralarındaki fark bir üstünlük sıralaması değil, hangi tarafın ne kadar karar
verdiğidir: kaynak temelli tasarımda yanıtın biçimine sunucu, sorgu temelli tasarımda
istemci karar verir.

**Kaynak ve sözleşme tasarımı** kaynak temelli yaklaşımı sonuna kadar götürdü. Kaynak
modeli, adres düzeni, yöntem seçimi, durum kodu eşlemesi, gövde adlandırması, sayfalama,
filtreleme, kısmi yanıt, etkisizlik anahtarları ve bağlantı odaklı yanıtlar — hepsi tek bir
soruya hizmet etti: istemci sunucunun ne yapacağını tahmin etmek zorunda kalmasın.

**Hata, sürüm ve belge** sözleşmenin zaman içindeki hâlini kurdu. Hata gövdesi tek biçime
bağlandı, doğrulama hataları alan düzeyinde bildirildi, sürümleme biçemlerinin gerçek
farkının aracılarda olduğu ölçüldü, kırıcılık istek ve yanıt yönünde ters kurallarla
tanımlandı ve sürüm numarası şema farkından türetildi. Sonra sözleşme makine okunur hâle
geldi: aynı tanımdan doğrulayıcı, belge, sözleşme sınaması ve sahte sunucu üretildi. Bu
konunun kalıcı sonucu tek cümleye sığar — **sözleşme, yazıldığı yerde denetlenebiliyorsa
sözleşmedir.**

**GraphQL ayrıntısı** sorgu temelli yaklaşımı ölçerek anlattı ve bunu kütüphaneye
başvurmadan, kendi ayrıştırıcısını ve çalıştırıcısını yazarak yaptı. Tip sistemi sorguyu
çalıştırmadan denetledi; üç işlem türünün yürütme kuralı tek kopyalı kitap üzerinde
ölçüldü; çözücü çağrısı ile veri kaynağı turu ayrıldı ve N+1 sorunu 101 turdan 3 tura
indirildi; parçaların çalışma anında yok olduğu, değişkenlerin ayrı belge sayısını
sabitlediği gösterildi; imleç sayfalamanın ofsete üstünlüğü liste değişirken ölçüldü; alan
düzeyi yetkilendirme ve kısmi hata kuruldu; zorunluluk işaretinin bir alanın çökmesini bütün
yanıta yayabildiği gösterildi; ve maliyet sınırının derinlik sınırından neden daha temel
olduğu sayıyla konuldu.

İki yaklaşımın ortak dersi de burada görünür. Kaynak temelli tasarımda tüketicinin ne
istediğini sunucu tahmin eder ve fazla ya da eksik veri gönderir; sorgu temelli tasarımda
tüketici tam olarak istediğini alır ama sunucu ne kadar iş yapacağını önceden bilemez. İlki
tasarım zamanında, ikincisi çalışma zamanında ödenen bir bedeldir. Sözleşme testleri
birinciyi, maliyet sınırı ikinciyi ölçülebilir kılar.

Bu kurs boyunca bir soru sürekli ertelendi. Ödünç kaydının notunu yalnız kaydın açıldığı
şubenin görmesi gerektiğini yazdık ama isteği yapanın gerçekten o şubenin görevlisi olduğunu
nereden bildiğimizi hiç sormadık; yetki kuralları bağlamdaki `kullanici` ve `rol`
alanlarını okudu ve o alanların oraya nasıl geldiği açıkta kaldı. Kullanımdan kaldırma
telemetrisinde tüketici kimliğinin bir başlıkla gelmesi de aynı boşluğun başka bir
görüntüsüydü — istemcinin kendi kimliğini bildirmesi, kimlik doğrulama değildir.

**Kimlik Doğrulama ve Yetkilendirme** kursu bu boşluğu doldurur. Kimliğin nasıl kanıtlandığı
ile o kimliğe neyin izin verildiğini birbirinden ayırır; parola ve belirteç saklamanın
güvenli gerçekleştirimini kurar; ve yetki modelini rol, öznitelik ya da ilişki temelinde
tanımlamanın ölçülebilir sonuçlarını karşılaştırır. Bu kursta yazdığımız yetki kuralları
oraya taşındığında, bağlamın nasıl doldurulduğu artık bir varsayım olmayacak.
