---
title: 'Durum Kodu Seçimi'
source: 'https://academia.sh/tr/kurslar/api-tasarimi/durum-kodu-secimi'
course: 'Web API Tasarımı'
language: tr
updated: '2026-08-17T18:06:45+00:00'
license: 'CC BY-SA 4.0'
---

# Durum Kodu Seçimi

Aynı senaryo kümesinin iki farklı kod eşlemesiyle yanıtlanması ve farkın istemcide ölçülmesi: yakın kodların ayrımı, 2xx ailesinin anlamları, yanlış eşlemenin ürettiği sessiz başarı ve boşa giden yeniden denemeler.

Bir önceki ders yöntemin taşıdığı sözleri ölçtü ve bir eksikle bitti: eşleşmeyen her şeye
404 dönen bir sunucu, kaynağın olmamasıyla yöntemin uygulanamamasını aynı sayıya
indirgiyordu. Aynı ders boyunca 201, 204 ve 409 kodları da gerekçe verilmeden kullanıldı.

Bu ders o gerekçeyi kurar. Durum kodu, yanıtın istemci tarafından **yorumlanmadan
okunabilen** tek bölümüdür. Gövdedeki `hata` alanı insan tarafından okunmak üzere yazılır
ve her serviste farklıdır; durum kodu ise standarttır ve istek katmanı, önbellek, yeniden
deneme düzeneği gibi bileşenler kararlarını ona bakarak verir. Kodun yanlış seçilmesi, bu
bileşenlerin yanlış karar vermesi demektir — ve bu, ölçülebilir bir farktır.

## Aynı Senaryolar, İki Eşleme

Aşağıdaki sunucu tek bir dosyadır ve iki kipte çalışır. `dogru` kipinde her sonuç kendi
koduyla bildirilir. `gevsek` kipinde ise yaygın bir kolaycılık uygulanır: bulunamayan şeyler
için 200 ve gövdede bir `hata` alanı, geri kalan her başarısızlık için 500.

```js
// kod-sunucusu.mjs — ayni senaryolari iki farkli kod esleme kipinde yanitlar
// Kullanim: node kod-sunucusu.mjs <baglanti-noktasi> <dogru|gevsek>
import { createServer } from "node:http";
import { DatabaseSync } from "node:sqlite";

const [NOKTA, KIP] = [Number(process.argv[2]), process.argv[3]];
const UYE_SINIRI = 2;
const db = new DatabaseSync("kutuphane.db");

const govdeOku = (istek) => new Promise((coz) => {
  let veri = ""; istek.on("data", (p) => (veri += p));
  istek.on("end", () => coz(veri));
});
const gonder = (yanit, kod, nesne, ekBaslik = {}) => {
  yanit.writeHead(kod, { "content-type": "application/json; charset=utf-8", ...ekBaslik });
  yanit.end(nesne === null ? "" : JSON.stringify(nesne));
};
// Gevsek kip: her sonucu 200 ya da 500 ile bildirir, ayrimi govdeye birakir.
const bildir = (yanit, kod, nesne, ekBaslik = {}) => {
  if (KIP === "dogru") return gonder(yanit, kod, nesne, ekBaslik);
  if (kod >= 400 && kod < 500) return gonder(yanit, kod === 404 ? 200 : 500, nesne ?? {});
  return gonder(yanit, 200, nesne ?? { sonuc: "tamam" });
};

const sunucu = createServer(async (istek, yanit) => {
  const yol = new URL(istek.url, "http://127.0.0.1").pathname;
  const kitap = /^\/kitaplar\/([^/]+)$/.exec(yol);
  const odunc = /^\/oduncler\/(\d+)$/.exec(yol);

  if (istek.method === "GET" && kitap) {
    const k = db.prepare("SELECT * FROM kitap WHERE isbn = ?").get(kitap[1]);
    return k ? bildir(yanit, 200, k) : bildir(yanit, 404, { hata: "kitap_yok" });
  }

  if (istek.method === "POST" && yol === "/oduncler") {
    const tur = istek.headers["content-type"] ?? "";
    if (!tur.startsWith("application/json"))
      return bildir(yanit, 415, { hata: "desteklenmeyen_tur", beklenen: "application/json" });
    const ham = await govdeOku(istek);
    let g;
    try { g = JSON.parse(ham); }
    catch { return bildir(yanit, 400, { hata: "govde_cozumlenemedi" }); }

    if (!db.prepare("SELECT 1 FROM kitap WHERE isbn = ?").get(g.isbn))
      return bildir(yanit, 422, { hata: "dogrulama", alan: "isbn", neden: "katalogda_yok" });

    const acik = db.prepare("SELECT COUNT(*) AS n FROM odunc WHERE uye = ? AND iade IS NULL").get(g.uye).n;
    if (acik >= UYE_SINIRI)
      return bildir(yanit, 409, { hata: "uye_siniri", sinir: UYE_SINIRI, acik });

    const s = db.prepare("INSERT INTO odunc (uye, isbn, verilis, iade) VALUES (?,?,?,NULL)")
                .run(g.uye, g.isbn, g.verilis);
    const id = Number(s.lastInsertRowid);
    return bildir(yanit, 201, { id }, { location: `/oduncler/${id}` });
  }

  if (istek.method === "DELETE" && odunc) {
    db.prepare("DELETE FROM odunc WHERE id = ?").run(Number(odunc[1]));
    return bildir(yanit, 204, null);
  }

  // Kaynak var ama yontem uygulanamiyor: bulunamadi degil.
  if (/^\/uyeler\/[^/]+\/durum$/.test(yol))
    return bildir(yanit, 405, { hata: "yontem_uygulanamaz" }, { allow: "GET" });

  bildir(yanit, 404, { hata: "yol_yok" });
});

sunucu.listen(NOKTA, "127.0.0.1", () => console.log(`${KIP} kip 127.0.0.1:${NOKTA}`));
```

Karşı tarafta, Uygulama Mimarisi kursundaki istek katmanının sadeleştirilmiş bir sürümü
durur. Tek kuralı vardır: 5xx sınıfı yeniden denenebilir sayılır, 4xx sınıfı sayılmaz.

```js
// istemci.mjs — durum koduna gore karar veren istemci; yeniden denemeleri sayar
// Kullanim: node istemci.mjs <taban-adres>
const TABAN = process.argv[2];
const JSON_BASLIK = { "content-type": "application/json" };

const SENARYOLAR = [
  ["oku",     "GET",    "/kitaplar/978-0131103627", null, null],
  ["yok",     "GET",    "/kitaplar/000-0000000000", null, null],
  ["olustur", "POST",   "/oduncler", JSON_BASLIK, '{"uye":"U-1002","isbn":"978-0131103627","verilis":"2026-03-10"}'],
  ["sinir",   "POST",   "/oduncler", JSON_BASLIK, '{"uye":"U-1002","isbn":"978-0131103627","verilis":"2026-03-11"}'],
  ["isbn",    "POST",   "/oduncler", JSON_BASLIK, '{"uye":"U-1001","isbn":"000-0000000000","verilis":"2026-03-10"}'],
  ["bozuk",   "POST",   "/oduncler", JSON_BASLIK, "{bozuk"],
  ["tur",     "POST",   "/oduncler", { "content-type": "text/plain" }, "merhaba"],
  ["sil",     "DELETE", "/oduncler/2", null, null],
  ["yontem",  "DELETE", "/uyeler/U-1001/durum", null, null],
];

let toplamIstek = 0, sessizBasari = 0;
for (const [ad, yontem, yol, baslik, govde] of SENARYOLAR) {
  let deneme = 0, yanit;
  do {
    deneme++; toplamIstek++;
    yanit = await fetch(TABAN + yol, { method: yontem, headers: baslik ?? {}, body: govde });
  } while (yanit.status >= 500 && deneme < 4);

  const metin = await yanit.text();
  const basarili = yanit.status < 400;
  const govdedeHata = metin.includes('"hata"');
  if (basarili && govdedeHata) sessizBasari++;
  const konum = yanit.headers.get("location");
  console.log(
    `${ad.padEnd(9)} ${String(yanit.status).padEnd(4)} ` +
    `${(basarili ? "basari" : "hata").padEnd(7)} deneme: ${deneme}` +
    (konum ? `  location: ${konum}` : ""),
  );
}
console.log(`toplam istek: ${toplamIstek}, basari sayilan hata: ${sessizBasari}`);
```

```bash
# Ayni istemci iki kipe karsi calistirilir; istek sayilari karsilastirilir.
kur() { rm -f kutuphane.db && sqlite3 kutuphane.db < sema.sql; }

kur; node kod-sunucusu.mjs 8477 dogru & a=$!; sleep 0.4
echo "--- dogru kip ---"; node istemci.mjs http://127.0.0.1:8477
kill $a

kur; node kod-sunucusu.mjs 8478 gevsek & b=$!; sleep 0.4
echo "--- gevsek kip ---"; node istemci.mjs http://127.0.0.1:8478
kill $b
```

```
dogru kip 127.0.0.1:8477
--- dogru kip ---
oku       200  basari  deneme: 1
yok       404  hata    deneme: 1
olustur   201  basari  deneme: 1  location: /oduncler/4
sinir     409  hata    deneme: 1
isbn      422  hata    deneme: 1
bozuk     400  hata    deneme: 1
tur       415  hata    deneme: 1
sil       204  basari  deneme: 1
yontem    405  hata    deneme: 1
toplam istek: 9, basari sayilan hata: 0
gevsek kip 127.0.0.1:8478
--- gevsek kip ---
oku       200  basari  deneme: 1
yok       200  basari  deneme: 1
olustur   200  basari  deneme: 1
sinir     500  hata    deneme: 4
isbn      500  hata    deneme: 4
bozuk     500  hata    deneme: 4
tur       500  hata    deneme: 4
sil       200  basari  deneme: 1
yontem    500  hata    deneme: 4
toplam istek: 24, basari sayilan hata: 1
```

## Ölçümün Okunması

Dokuz senaryo, doğru eşlemede dokuz istekle bitti; gevşek eşlemede yirmi dört istek gerekti.
Fark, dört senaryonun yeniden denenmiş olmasından gelir. Bu dört senaryonun ortak yanı
şudur: **hiçbiri tekrarlanınca düzelmez.** Üye sınırı doludur, ISBN katalogda yoktur, gövde
bozuktur, içerik türü yanlıştır. Sunucu bunları 500 ile bildirdiğinde istemciye "bende
geçici bir sorun var, tekrar dene" demiş olur. İstemci sözü tutar ve üç kez daha dener.
İstek üçe katlanır, sonuç değişmez.

İkinci sayı daha sinsidir. Gevşek eşlemede bir başarısızlık **başarı sayıldı**: olmayan bir
kitabı isteyen istemci 200 aldı ve gövdedeki `hata` alanını okumadığı için elinde bir kitap
olduğunu sandı. Ekranda boş bir kitap kartı, kütüğe yazılmayan bir hata ve bulunamayan bir
kusur bu satırdan doğar.

Üçüncü fark satırlarda değil, eksik olan şeydedir. Doğru eşlemede `olustur` senaryosu 201
ile birlikte `Location` başlığını taşıyor; gevşek eşlemede o başlık yok. Yeni kaynağın
adresini bildiren tek yer odur. Onsuz istemci, oluşturduğu ödünç kaydına ulaşmak için ya
gövdedeki kimliği okuyup adresi kendisi kurmak ya da koleksiyonu yeniden çekmek zorunda
kalır.

## Yakın Kodların Ayrımı

Kod seçimini yapan soru şudur: **isteğin hangi bölümü kabul edilmedi?**

- **400** — Gövde okunamadı. İstek, biçim düzeyinde bozuktur; JSON çözümlenemedi, zorunlu
  bir başlık eksik. Sunucu isteği anlamlandıramadığı için içeriğe dair bir şey söyleyemez.
- **415** — Gövde okunabilir olabilir ama içerik türü desteklenmiyor. Sorun içerikte değil,
  içeriğin hangi biçimde gönderildiğindedir.
- **422** — Gövde çözümlendi, alanlar okundu, ama değerler kabul edilmedi. Katalogda
  olmayan bir ISBN böyledir: yazım doğru, gönderilen değer geçersiz. Bu ayrımın pratik
  değeri, istemcinin hatayı forma geri bağlayabilmesidir — hangi alanın reddedildiği
  söylenebilir. Alan düzeyinde hata bildirimi ayrı bir dersin konusudur.
- **409** — Gövde de değerler de geçerli, ama sistemin o anki durumu isteğin yerine
  getirilmesine izin vermiyor. Üye sınırının dolu olması budur. 422 ile 409 arasındaki
  fark zamandır: 422 aynı gövde için hep geçersizdir, 409 durum değişince geçerli olabilir.
- **404** — Adreslenen kaynak yok. Sorun gövdede değil yoldadır.
- **405** — Kaynak var, yöntem uygulanamıyor. Yanıt, hangi yöntemlerin kabul edildiğini
  `Allow` başlığıyla bildirir; yukarıdaki sunucu `Allow: GET` gönderir. 404 ile karıştırmak
  istemciyi yanlış yere bakmaya iter: adresi düzeltmeye çalışır, oysa yöntemi düzeltmesi
  gerekir.

Kimlik ve yetki kodları — 401 ile 403 — bu listeye bilinçli olarak alınmadı. İkisi
arasındaki ayrım kimliğin bilinip bilinmediğine dayanır ve Kimlik Doğrulama ve Yetkilendirme
kursunda ele alınır. Buradaki tek kural, ikisinin de 404'e indirgenmemesidir.

## 2xx Ailesi

Başarı da tek bir kod değildir.

**200** genel başarıdır ve gövde taşır. **201** yeni bir kaynak oluşturulduğunu söyler;
yanında `Location` başlığı bulunmalıdır, çünkü istemci o adresi kendisi kuramaz. **204**
başarıyı gövdesiz bildirir; silme işlemlerinde ve döndürülecek yeni bir gösterim olmadığında
kullanılır. **202** isteğin kabul edildiğini ama henüz tamamlanmadığını söyler; işin
sonucunu izlemek için ayrı bir kaynak gerektirir ve eşzamansız işleme konusuna girer.

Seçimin ölçütü yine istemcinin kararıdır. 204 alan bir istemci gövdeyi okumaya çalışmaz;
200 alan istemci okur. Silme yanıtına boş bir gövdeyle 200 dönmek, istemciyi olmayan bir
gövdeyi çözümlemeye zorlar ve gövde hatası üretir.

## Bilinmeyeni 500'e Toplamak

Gevşek kipin asıl kusuru 500 kodunu kullanması değil, **her şey için** kullanmasıdır. 500,
sunucunun kendi kusurunu bildirdiği koddur: beklenmeyen bir özel durum, erişilemeyen bir
veritabanı, çözülmemiş bir varsayım. Bu kodu istemcinin hatası için kullanmak iki şeyi aynı
anda bozar. İstemci, düzeltebileceği bir sorunu düzeltmez — çünkü kendisine sorunun onda
olmadığı söylenmiştir. Sunucu tarafında ise gerçek kusurların sayısı, istemci hatalarıyla
karışır ve ölçülemez hâle gelir.

Doğru davranış, bilinen her başarısızlığı kendi koduna eşlemek ve 500'ü yalnızca
sınıflandırılamayan durum için saklamaktır. Sınıflandırılamayan durumun gövdesinde de
ayrıntı verilmez; iç hata iletisini dışarıya yazmak, sunucunun iç yapısını sözleşmeye
sızdırır.

## Özet

- Durum kodu, yanıtın istemci tarafından yorumlanmadan okunan bölümüdür; önbellek, istek
  katmanı ve yeniden deneme düzeneği kararlarını ona bakarak verir.
- Aynı dokuz senaryo doğru eşlemede dokuz istekle bitti, gevşek eşlemede yirmi dört istek
  üretti; fark, düzelmeyecek hataların 5xx ile bildirilip yeniden denenmesinden doğar.
- Gevşek eşlemede bir başarısızlık başarı sayıldı: 200 ile dönen "bulunamadı", istemcinin
  hiç fark etmediği bir hataya dönüştü.
- Hata kodu seçimi isteğin hangi bölümünün kabul edilmediğine bakar: 400 biçim, 415 içerik
  türü, 422 alan değerleri, 409 sistemin o anki durumu, 404 adres, 405 yöntem.
- 201 yanıtı `Location` başlığı olmadan eksiktir; 204 gövdesiz başarıyı bildirir ve
  istemciyi gövde çözümlemeye zorlamaz.
- 500 yalnızca sunucunun sınıflandıramadığı kusur için ayrılır; istemci hataları için
  kullanıldığında hem istemci yanlış yönlendirilir hem de gerçek kusurlar ölçülemez olur.

## Sonraki Adım

Kodlar yerine oturdu, ama yanıtların içine ne konacağı hâlâ açık. Bu derste gövdeler
gelişigüzel yazıldı: bir yerde yalnız bir kimlik alanı, başka bir yerde hata adı ve sınır
değeri. Alan adları hangi yazım biçiminde olacak, tarihler nasıl yazılacak, boş
değerle eksik alan aynı şey mi sayılacak, koleksiyon yanıtı doğrudan bir dizi mi olacak?
Sonraki ders gövde biçimini ele alır ve yanlış seçimlerin nerede sessizce veri
bozduğunu — büyük sayıların çözümlenmesinde ve tarih yazımında — çalıştırarak gösterir.
