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

# GraphQL

Tek uç nokta üzerinden istemci güdümlü sorgu: sunucunun yayımladığı tip sistemi, sorgunun yürütülmeden önce şemaya göre doğrulanması, alan başına çözümleme ve bir alanın hata vermesiyle oluşan kısmen başarılı yanıt; değişkenlerle aynı belgenin yeniden kullanımı.

Önceki ders sözleşmeyi şemada sabitleyen yaklaşımı kurdu ve bir eksiğini bıraktı: yanıtın
biçimi sunucunun kararı olduğu için, istemcinin gereksinimi değiştiğinde sunucu değişmek
zorundadır. İkinci derste ölçülen sorun buydu.

**GraphQL** bunun tersini yapar. Yanıtın biçimini istemci belirler, ama bunu serbest bir
metinle değil, sunucunun yayımladığı bir **tip sistemi** (type system) içinde yapar. Ortada
tek bir uç nokta vardır; ne istendiği adreste değil, gövdedeki **sorgu belgesinde** (query
document) yazar. Bu ders o düzeneği küçük bir işleyici yazarak kurar.

## Tip Sistemi Sunucunun Yayımladığı Yüzeydir

Tip sistemi, hangi alanların bulunduğunu, her alanın hangi tipte olduğunu ve hangi alanların
alt seçim gerektirdiğini söyler. Her alanın verisini nasıl bulacağını bilen işleve **çözücü**
(resolver) denir; bir alanın çözücüsü yoksa değer kaynak nesneden okunur.

```js
// sema.mjs — tip sistemi ve cozuculer. Sunucunun yayimladigi yuzeyin tamami buradadir.
export const TIPLER = {
  Sorgu: { uye: { tip: "Uye", arg: "id" }, kitap: { tip: "Kitap", arg: "isbn" } },
  Uye:   { id: "ID", ad: "Dizgi", eposta: "Dizgi", ceza: "Sayi", oduncler: { tip: "Odunc", liste: true } },
  Odunc: { id: "ID", sonTarih: "Dizgi", gecikmeGunu: "Sayi", kitap: { tip: "Kitap" } },
  Kitap: { isbn: "ID", ad: "Dizgi", yazar: "Dizgi", sayfa: "Sayi" },
};

const UYELER = [{ id: "u1", ad: "Ayse Kaya", eposta: "ayse@ornek.test", ceza: 0 }];
const KITAPLAR = {
  "978-0262033848": { isbn: "978-0262033848", ad: "Algoritmalara Giris", yazar: "Cormen", sayfa: 1312 },
  "978-0201896831": { isbn: "978-0201896831", ad: "Programlama Sanati", yazar: "Knuth", sayfa: 650 },
};
const ODUNCLER = [{ id: "o11", uye: "u1", isbn: "978-0262033848", sonTarih: "2024-05-15" },
                  { id: "o12", uye: "u1", isbn: "978-0201896831", sonTarih: "2024-05-17" }];

// Cozucu: her alan kendi verisini kendi getirir. Bir alanin hata vermesi digerlerini durdurmaz.
export const COZUCULER = {
  "Sorgu.uye": (_, arg) => UYELER.find((u) => u.id === arg),
  "Sorgu.kitap": (_, arg) => KITAPLAR[arg],
  "Uye.oduncler": (uye) => ODUNCLER.filter((o) => o.uye === uye.id),
  "Odunc.kitap": (odunc) => KITAPLAR[odunc.isbn],
  "Odunc.gecikmeGunu": () => { throw new Error("gecikme servisi yanit vermiyor"); },
};
```

`Odunc.gecikmeGunu` çözücüsünün hata atması bilinçlidir: gecikme tutarı gerçek bir kurulumda
ayrı bir servisten gelir ve o servis yanıt vermeyebilir. Bu alan, kısmen başarılı yanıtı
göstermek için oradadır.

## İşleyicinin Üç Adımı

Sorgu belgesi üç adımdan geçer: **çözümleme** (metin bir seçim ağacına dönüşür),
**doğrulama** (ağaç tip sistemiyle karşılaştırılır) ve **yürütme** (her alan kendi çözücüsüyle
hesaplanır). Doğrulamanın yürütmeden önce gelmesi belirleyicidir: geçersiz bir sorgu için
hiçbir çözücü çalışmaz, dolayısıyla hiçbir veri erişimi yapılmaz.

```js
// yurut.mjs — kucuk sorgu isleyicisi: cozumleme, tip sistemine gore dogrulama, alan basina yurutme
import { TIPLER, COZUCULER } from "./sema.mjs";

export const cozumle = (metin) => {                    // sorgu belgesini secim agacina cevirir
  const s = metin.match(/[A-Za-z_][A-Za-z0-9_]*|[{}():]|"[^"]*"|\d+/g) ?? [];
  let i = 0;
  const kume = () => {
    const alanlar = [];
    i++;
    while (s[i] !== "}") {
      const ad = s[i++];
      let arg = null;
      if (s[i] === "(") { arg = s[i + 3].replace(/^"|"$/g, ""); i += 5; }   // ad ( anahtar : deger )
      alanlar.push({ ad, arg, alt: s[i] === "{" ? kume() : null });
    }
    i++;
    return alanlar;
  };
  s.unshift("{"); s.push("}");
  return kume();
};

export const dogrula = (alanlar, tipAdi = "Sorgu", yol = []) => {   // tip sistemine gore denetim
  const kusur = [];
  for (const alan of alanlar) {
    const tanim = TIPLER[tipAdi]?.[alan.ad];
    const su = [...yol, alan.ad];
    if (!tanim) { kusur.push(`${su.join(".")}: ${tipAdi} tipinde boyle bir alan yok`); continue; }
    const nesneMi = typeof tanim === "object";
    if (nesneMi && !alan.alt) kusur.push(`${su.join(".")}: ${tanim.tip} bir nesne, alt alan secilmeli`);
    else if (!nesneMi && alan.alt) kusur.push(`${su.join(".")}: ${tanim} bir skaler, alt alan secilemez`);
    else if (nesneMi) kusur.push(...dogrula(alan.alt, tanim.tip, su));
  }
  return kusur;
};

export const yurut = (alanlar, kok) => {
  const hatalar = [];
  const adim = (alanlar, kaynak, tipAdi, yol) => {
    const cikti = {};
    for (const alan of alanlar) {
      const tanim = TIPLER[tipAdi][alan.ad];
      const su = [...yol, alan.ad];
      const cozucu = COZUCULER[`${tipAdi}.${alan.ad}`] ?? ((k) => k[alan.ad]);
      let deger;
      // Alan basina cozumleme: bir alanin hatasi digerlerini durdurmaz, o alan null olur.
      try { deger = cozucu(kaynak, alan.arg); }
      catch (e) { hatalar.push({ yol: su.join("."), ileti: e.message }); cikti[alan.ad] = null; continue; }
      if (typeof tanim !== "object") { cikti[alan.ad] = deger ?? null; continue; }
      cikti[alan.ad] = tanim.liste
        ? deger.map((d, k) => adim(alan.alt, d, tanim.tip, [...su, k]))
        : adim(alan.alt, deger, tanim.tip, su);
    }
    return cikti;
  };
  return { veri: adim(alanlar, kok, "Sorgu", []), hatalar };
};

export const degiskenleriYerlestir = (belge, degiskenler) =>
  belge.replace(/\$(\w+)/g, (_, ad) => JSON.stringify(degiskenler[ad]));
```

```js
// sunucu.mjs — tek uc nokta. Yol her sorgu icin aynidir; ne istendigi govdede yazar.
import { createServer } from "node:http";
import { cozumle, dogrula, yurut, degiskenleriYerlestir } from "./yurut.mjs";

createServer(async (istek, yanit) => {
  yanit.sendDate = false;
  let ham = "";
  for await (const parca of istek) ham += parca;
  const { belge, degiskenler = {} } = JSON.parse(ham);

  const alanlar = cozumle(degiskenleriYerlestir(belge, degiskenler));
  const kusur = dogrula(alanlar);
  // Dogrulama yurutmeden once yapilir: gecersiz sorgu icin hicbir cozucu calismaz.
  const govde = kusur.length ? { hatalar: kusur } : yurut(alanlar, null);
  const metin = JSON.stringify(govde);
  yanit.setHeader("Content-Type", "application/json; charset=utf-8");
  yanit.setHeader("Content-Length", Buffer.byteLength(metin));
  yanit.writeHead(kusur.length ? 400 : 200).end(metin);
}).listen(8471, "127.0.0.1", () => console.log("tek uc nokta 127.0.0.1:8471/graf"));
```

## Ölçüm

```js
// olc.mjs — tek uc noktanin dort ozelligini olcer
const sor = async (belge, degiskenler) => {
  const govde = JSON.stringify({ belge, degiskenler });
  const y = await fetch("http://127.0.0.1:8471/graf", { method: "POST", body: govde });
  const metin = await y.text();
  return { kod: y.status, bayt: Buffer.byteLength(metin), gonderilen: Buffer.byteLength(govde),
           govde: JSON.parse(metin) };
};
const yaz = (baslik, y) => console.log(`  ${baslik.padEnd(22)} HTTP ${y.kod}  ` +
  `gonderilen=${String(y.gonderilen).padStart(3)} B alinan=${String(y.bayt).padStart(3)} B\n` +
  `    ${JSON.stringify(y.govde)}`);

console.log("-- 1) ayni uc nokta, iki farkli istemci gereksinimi --");
yaz("raf ekrani", await sor('uye(id: "u1") { ad }'));
yaz("gecikme raporu", await sor('uye(id: "u1") { ad oduncler { sonTarih kitap { ad yazar } } }'));

console.log("\n-- 2) tip sistemi: sorgu yurutulmeden once dogrulanir --");
yaz("olmayan alan", await sor('uye(id: "u1") { ad telefon }'));
yaz("skalerde alt secim", await sor('uye(id: "u1") { ad { uzunluk } }'));
yaz("nesnede secim yok", await sor('uye(id: "u1") { ad oduncler }'));

console.log("\n-- 3) kismi basari: bir alanin cozucusu hata veriyor --");
yaz("gecikme alani", await sor('uye(id: "u1") { ad oduncler { sonTarih gecikmeGunu } }'));

console.log("\n-- 4) degiskenler: ayni belge, iki farkli deger --");
const BELGE = "kitap(isbn: $kimlik) { ad yazar sayfa }";
for (const kimlik of ["978-0262033848", "978-0201896831"])
  yaz(`kimlik=${kimlik.slice(-4)}`, await sor(BELGE, { kimlik }));
```

```bash
node sunucu.mjs & p=$!
curl -s --retry 20 --retry-connrefused --retry-delay 0 -o /dev/null \
  -X POST -d '{"belge":"kitap(isbn: \"978-0262033848\") { ad }"}' http://127.0.0.1:8471/graf
node olc.mjs
kill $p
```

```
tek uc nokta 127.0.0.1:8471/graf
-- 1) ayni uc nokta, iki farkli istemci gereksinimi --
  raf ekrani             HTTP 200  gonderilen= 34 B alinan= 48 B
    {"veri":{"uye":{"ad":"Ayse Kaya"}},"hatalar":[]}
  gecikme raporu         HTTP 200  gonderilen= 75 B alinan=219 B
    {"veri":{"uye":{"ad":"Ayse Kaya","oduncler":[{"sonTarih":"2024-05-15","kitap":{"ad":"Algoritmalara Giris","yazar":"Cormen"}},{"sonTarih":"2024-05-17","kitap":{"ad":"Programlama Sanati","yazar":"Knuth"}}]}},"hatalar":[]}

-- 2) tip sistemi: sorgu yurutulmeden once dogrulanir --
  olmayan alan           HTTP 400  gonderilen= 42 B alinan= 59 B
    {"hatalar":["uye.telefon: Uye tipinde boyle bir alan yok"]}
  skalerde alt secim     HTTP 400  gonderilen= 46 B alinan= 60 B
    {"hatalar":["uye.ad: Dizgi bir skaler, alt alan secilemez"]}
  nesnede secim yok      HTTP 400  gonderilen= 43 B alinan= 65 B
    {"hatalar":["uye.oduncler: Odunc bir nesne, alt alan secilmeli"]}

-- 3) kismi basari: bir alanin cozucusu hata veriyor --
  gecikme alani          HTTP 200  gonderilen= 68 B alinan=306 B
    {"veri":{"uye":{"ad":"Ayse Kaya","oduncler":[{"sonTarih":"2024-05-15","gecikmeGunu":null},{"sonTarih":"2024-05-17","gecikmeGunu":null}]}},"hatalar":[{"yol":"uye.oduncler.0.gecikmeGunu","ileti":"gecikme servisi yanit vermiyor"},{"yol":"uye.oduncler.1.gecikmeGunu","ileti":"gecikme servisi yanit vermiyor"}]}

-- 4) degiskenler: ayni belge, iki farkli deger --
  kimlik=3848            HTTP 200  gonderilen= 93 B alinan= 90 B
    {"veri":{"kitap":{"ad":"Algoritmalara Giris","yazar":"Cormen","sayfa":1312}},"hatalar":[]}
  kimlik=6831            HTTP 200  gonderilen= 93 B alinan= 87 B
    {"veri":{"kitap":{"ad":"Programlama Sanati","yazar":"Knuth","sayfa":650}},"hatalar":[]}
```

## Ölçümün Söyledikleri

**Tek uç nokta, iki farklı yanıt biçimi.** Raf ekranı 48 bayt, gecikme raporu 219 bayt almıştır.
İkisi de aynı adrese gitmiştir ve sunucuda hiçbir şey değişmemiştir. İkinci derste sorgu
temelli biçemin gösterdiği özellik budur; buradaki fark, seçimin serbest bir metin değil,
yayımlanmış bir tip sistemi içinde yapılmasıdır.

**Tip sistemi üç ayrı hatayı yürütmeden önce yakalar.** Var olmayan bir alan, bir skalerin
altında alt seçim yapılması ve bir nesnenin alt seçimsiz istenmesi — üçü de `400` ile
reddedilir ve hiçbir çözücü çalışmaz. Bu, sorgu temelli bir arayüzün gevşek olmadığını
gösterir: istemci alan bileşimini seçebilir, ama şemanın dışına çıkamaz.

**Kısmen başarılı yanıt bir HTTP hatası değildir.** Gecikme alanının çözücüsü hata atmıştır,
ama yanıt `200` gelmiştir. Veri kısmında istenen alanların çoğu doludur, hata veren alan
`null` olmuştur ve hata listesi her başarısızlığı **yolu**yla birlikte bildirir:
`uye.oduncler.0.gecikmeGunu`. Bu davranış alan başına çözümlemenin doğrudan sonucudur —
her alan kendi verisini kendi getirdiği için bir alanın başarısızlığı komşusunu etkilemez.

Bunun tüketici tarafındaki karşılığı, HTTP durum kodunun tek başına yeterli bir gösterge
olmamasıdır. `200` alan bir istemci gövdeyi de incelemek zorundadır. Uygulama Mimarisi
kursunda kısmi başarı kavramı istemci tarafından anlatılmıştı; burada aynı davranışın
sunucudaki kaynağı görünür.

**Değişkenler sorgu belgesini yeniden kullanılabilir kılar.** Aynı belge iki farklı kitap için
çalışmıştır. Değişkenlerin ayrı gönderilmesinin ikinci bir yararı da vardır: belge sabit
kaldığı için sunucu tarafında tanınabilir, kaydedilebilir ve maliyeti önceden hesaplanabilir.

## Bedeller

Bu esnekliğin dört bedeli vardır ve üçü ölçümde görünür.

**Önbellekleme zorlaşır.** Bütün sorgular tek adrese, `POST` yöntemiyle gider. Önceki dersteki
varlık etiketi düzeneği burada doğrudan çalışmaz, çünkü adres kaydı adlandırmaz. Önbellekleme
ya istemci tarafında, sorgu belgesi anahtar alınarak yapılır ya da alan düzeyinde kurulur.

**Yanıtın maliyeti önceden bilinmez.** Sunucu, gelen sorgunun kaç veri erişimi doğuracağını
sorgu gelmeden hesaplayamaz. Derinliği artan bir sorgu ya da uzun bir liste içinde iç içe
seçim, tek bir istekle çok iş yaptırabilir. Bu, sorgu maliyeti ve derinlik sınırı gibi
korumaları gerektirir.

**Alan başına çözümleme veri erişimini çoğaltır.** Ölçümdeki iki ödünç için `Odunc.kitap`
çözücüsü iki kez çalıştı; ödünç sayısı arttıkça bu sayı da artar. İkinci derste kaynak
temelli biçemde görülen N+1 örüntüsü burada sunucunun içine taşınmıştır. Toplu yükleme ile
çözümü bu kursun GraphQL Ayrıntısı konusunda ele alınır.

**Hata sözleşmesi ikiye bölünür.** Bir istekte hem taşıma düzeyinde durum kodu hem de gövde
içinde hata listesi bulunur. İkisinin ne zaman hangisini kullanacağı açıkça kararlaştırılmazsa,
tüketiciler hataları iki ayrı yerde aramak zorunda kalır.

## Özet

- GraphQL tek uç nokta üzerinden çalışır; ne istendiği adreste değil, gövdedeki sorgu
  belgesinde yazar ve yanıtın biçimini istemcinin seçim kümesi belirler.
- Sunucunun yayımladığı tip sistemi seçimi sınırlar: ölçümde var olmayan alan, skalerde alt
  seçim ve alt seçimsiz nesne yürütülmeden önce `400` ile reddedildi.
- Alan başına çözümleme, bir alanın çözücüsü hata verdiğinde diğerlerinin çalışmaya devam
  etmesini sağlar; sonuç `200` durumlu, veri ile hata listesini birlikte taşıyan kısmen
  başarılı bir yanıttır.
- Değişkenler belgeyi sabit tutar; aynı belge farklı değerlerle yeniden kullanılır ve sunucu
  tarafında tanınabilir hâle gelir.
- Bedeller ölçüme yansır: tek adres ve `POST` önbelleklemeyi zorlaştırır, yanıtın doğuracağı
  iş önceden bilinmez, alan başına çözümleme veri erişimini çoğaltır ve hata sözleşmesi iki
  yere bölünür.

## Sonraki Adım

Tek uç nokta üzerinden, gövdede taşınan bir işlem adıyla çalışmak yeni bir fikir değildir.
Aynı düzeneği kuran ve arkasında geniş bir miras bırakan bir protokol ailesi vardır: her
iletinin bir zarfa sarıldığı, işlem adının ve hatanın da o zarfın içinde taşındığı, sözleşmenin
ayrı bir tanım dosyasında yayımlandığı bir aile. Sonraki ders bu zarf tabanlı yaklaşımı kendi
küçük ayrıştırıcısıyla kurar, zarfın bayt maliyetini ölçer ve bugünkü arayüz kararlarının
hangilerinin oradan devralındığını gösterir.
