---
title: 'Hata İşleme Katmanı'
source: 'https://academia.sh/tr/kurslar/sunucu-temelleri/hata-isleme-katmani'
course: 'Sunucu Tarafı Temelleri'
language: tr
updated: '2026-08-17T18:06:52+00:00'
license: 'CC BY-SA 4.0'
---

# Hata İşleme Katmanı

İstem hatası ile programcı hatası ayrılır ve tek bir katman her ikisini de aynı yanıt biçimine çevirir; istemciye giden metinler sabit bir tablodan gelir, iç ayrıntı yalnız kütükte kalır ve yanıt gövdelerinin sızıntı içermediği yedi senaryoda sınanır.

Önceki dersin sunucusunda hata durumu tek bir dala sıkışmıştı: rafta bulunmayan kitap `409`
üretiyordu. Gerçek bir uygulamada hatalar iki ayrı kaynaktan gelir. Üye eksik alanla istek
gönderir; bu, uygulamanın öngördüğü bir durumdur ve yanıtı bilgilendiricidir. Uygulama
kendi kodundaki bir kusurla çöker; bu, öngörülmemiş bir durumdur ve yanıtı bilgi vermemelidir.

Bu ders iki kaynağı ayırır ve ikisini de tek bir katmandan geçirir. Sonuçta her hata aynı
biçimde yanıta dönüşür; aralarındaki fark, gövdenin ne kadarını taşıdığındadır.

## İki Hata Sınıfı

**İstem hatası** (client error) uygulamanın beklediği bir durumdur: eksik alan, bozuk gövde,
bulunmayan kaynak, çakışan durum. Bunlar kusur değildir; iş kuralının kendisidir. Yanıtları
`4xx` aralığındadır ve istemcinin ne yapması gerektiğini söylemelidir.

**Programcı hatası** (programmer error) uygulamanın kendi kusurudur: var olmayan bir fonksiyonun
çağrılması, tanımsız bir değerin okunması, tutulmayan bir varsayım. Bunların yanıtı `5xx`
aralığındadır ve istemciye söyleyecek anlamlı hiçbir şeyleri yoktur — çünkü istemcinin
yapabileceği bir şey yoktur.

Ayrımın Node.js Çalışma Zamanı kursunda kurulan **işletimsel hata** ve **programcı hatası**
ayrımıyla ilişkisi doğrudandır; buradaki fark, ayrımın bir HTTP yanıtına çevrilmesidir.

Aşağıdaki modül ayrımı bir sınıfla ve bir çeviri fonksiyonuyla kurar.

```js
// kaynak/kurulum/hatalar.mjs — istem hatasi ile programci hatasini ayirir ve yanita cevirir
export class IstemHatasi extends Error {
  constructor(kod, durum, ayrintilar = null) {
    super(`istem hatasi: ${kod}`);
    this.name = "IstemHatasi";
    this.kod = kod;                       // sabit tanimlayici; istemci buna gore dallanir
    this.durum = durum;
    this.ayrintilar = ayrintilar;         // istemciye gosterilmesi guvenli alanlar
  }
}

// Istemciye giden metinlerin tek kaynagi. Hata nesnesinin kendi iletisi yanita hic girmez.
const ILETILER = {
  govde_bicimsiz: "Istek govdesi JSON olarak cozulemedi.",
  alan_eksik: "Istek zorunlu alanlari tasimiyor.",
  kimlik_yok: "Gecerli bir uye kimligi gerekiyor.",
  kitap_yok: "Katalogda bu ISBN bulunmuyor.",
  raf_bos: "Kitabin rafta kopyasi kalmadi.",
  ic_hata: "Istek islenemedi.",
};

export const yanitaCevir = (hata, istekKimligi) => {
  const bilinen = hata instanceof IstemHatasi && hata.kod in ILETILER;
  const kod = bilinen ? hata.kod : "ic_hata";
  const govde = { hata: { kod, ileti: ILETILER[kod] }, istekKimligi };
  if (bilinen && hata.ayrintilar) govde.hata.ayrintilar = hata.ayrintilar;
  return { durum: bilinen ? hata.durum : 500, govde };
};
```

Üç karar bu otuz satırda gizlidir.

**İstemciye giden metin hatadan gelmez.** `yanitaCevir` fonksiyonu `hata.message` değerini
hiç okumaz; iletiyi sabit bir tablodan alır. Hata iletileri geliştirici için yazılır ve dosya
yolu, sorgu metni, iç değişken adı taşıyabilir. Tablodan gelen ileti bunu yapısal olarak
imkânsız kılar.

**Bilinmeyen her şey iç hatadır.** Koşul yalnız `IstemHatasi` örneklerini değil, kodu tabloda
bulunan örnekleri geçirir. Tabloya eklenmemiş bir kodla oluşturulmuş bir hata da `ic_hata`
olur; varsayılan, sızdırmama yönündedir.

**Ayrıntılar açıkça işaretlenir.** `ayrintilar` alanı yalnız hatayı fırlatan kodun bilerek
koyduğu değerleri taşır — eksik alan adları, sorgulanan ISBN. Bu, istemciye yardım eden bilgi
ile iç ayrıntı arasındaki sınırı bir alan adına bağlar.

## Tek Katmandan Geçirmek

Sunucunun bütün işleyicileri hata fırlatır; hiçbiri yanıt yazmaz. Yanıt yazma işi tek bir
`catch` bloğundadır. Bu, önceki bölümde kurulan kuralın uygulanmasıdır: hata yakalayıcı,
koruduğu her şeyin dışında durur.

```js
// kaynak/http/sunucu.mjs — tek bir hata katmani: her hata ayni bicimde yanita cevrilir
import { createServer } from "node:http";
import { IstemHatasi, yanitaCevir } from "../kurulum/hatalar.mjs";

const KATALOG = new Map([["978-0262033848", { rafta: 0 }], ["978-0201835953", { rafta: 3 }]]);
let sayac = 0;

const govdeyiCoz = (metin) => {
  try {
    return metin ? JSON.parse(metin) : {};
  } catch {
    throw new IstemHatasi("govde_bicimsiz", 400);
  }
};

const oduncVer = (istem, uye) => {
  if (!uye) throw new IstemHatasi("kimlik_yok", 401);
  const eksik = ["isbn"].filter((alan) => !istem[alan]);
  if (eksik.length > 0) throw new IstemHatasi("alan_eksik", 400, { eksikAlanlar: eksik });
  const kitap = KATALOG.get(istem.isbn);
  if (!kitap) throw new IstemHatasi("kitap_yok", 404, { isbn: istem.isbn });
  if (kitap.rafta < 1) throw new IstemHatasi("raf_bos", 409, { isbn: istem.isbn });
  kitap.rafta -= 1;
  return { odunc: { isbn: istem.isbn, uye }, kalan: kitap.rafta };
};

createServer(async (istek, yanit) => {
  yanit.sendDate = false;
  const kimlik = `i-${++sayac}`;
  const yaz = (durum, govde) => {
    const metin = JSON.stringify(govde);
    yanit.writeHead(durum, { "content-type": "application/json; charset=utf-8",
      "content-length": Buffer.byteLength(metin), "x-istek-kimligi": kimlik });
    yanit.end(metin);
  };

  try {
    const parcalar = [];
    for await (const parca of istek) parcalar.push(parca);
    const yol = new URL(istek.url, "http://yerel").pathname;
    const uye = (istek.headers.authorization ?? "").replace(/^Uye /, "") || null;

    if (yol === "/odunc") return yaz(201, oduncVer(govdeyiCoz(Buffer.concat(parcalar).toString()), uye));
    if (yol === "/rapor") return yaz(200, { toplam: KATALOG.gec().length });   // programci hatasi
    throw new IstemHatasi("kitap_yok", 404);
  } catch (hata) {
    const beklenen = hata instanceof IstemHatasi;
    console.log(JSON.stringify({                                   // ic ayrinti yalniz kutukte
      duzey: beklenen ? "uyari" : "hata", istekKimligi: kimlik,
      tur: beklenen ? "istem_hatasi" : "programci_hatasi",
      ad: hata.name, ileti: hata.message,
      yigit: beklenen ? null : ((hata.stack ?? "").split("\n")[1] ?? "")
        .trim().replace(/\(file:.*\/(?=[^\/]+:\d+:\d+\))/, "("),
    }));
    const { durum, govde } = yanitaCevir(hata, kimlik);
    yaz(durum, govde);
  }
}).listen(8439, "127.0.0.1", () => console.log(JSON.stringify({ olay: "acildi", baglantiNoktasi: 8439 })));
```

`/rapor` yolundaki `KATALOG.gec()` çağrısı var olmayan bir yöntemi çağırır ve gerçek bir
programcı hatası üretir; taklit edilmiş bir hata değil, yazım yanlışının doğal sonucudur.

Kütük kaydı iki hata sınıfını ayrı düzeylere koyar. İstem hataları `uyari` düzeyindedir ve
yığıt izi taşımaz — çünkü kodun nerede olduğu bilinen bir şeydir. Programcı hataları `hata`
düzeyindedir ve yığıt izinin ilk satırını taşır; kusuru bulmak için gereken tek bilgi budur.

## Yedi Senaryonun Sınanması

Yanıt biçiminin doğruluğu okunarak değil, bütün dalları çalıştırılarak doğrulanır. Aşağıdaki
sınama yedi senaryoyu sırayla dener, her yanıtın durum kodunu beklenenle karşılaştırır ve
gövdede yasaklı kalıp arar.

Sınamanın ikinci yarısı önemlidir: yanıtların temiz olması, uygulamanın hataları sessizce
yutmasıyla da sağlanabilirdi. Bunu dışlamak için sınama, iç ayrıntının kütükte **gerçekten
bulunduğunu** da doğrular. İki yön birlikte sınandığında geçen bir sınama anlamlıdır.

```js
// denetim/hata-yaniti.mjs — her hata yanitinin bicimini ve sizinti icermedigini sinar.
// Ayrica programci hatasinin ic ayrintisinin kutukte GERCEKTEN bulundugunu dogrular:
// sinamanin sessizce her seyi yutan bir uygulamayi onaylamamasi icin iki yon de gereklidir.
import { spawn } from "node:child_process";
import { setTimeout as bekle } from "node:timers/promises";

const YASAK = [/at\s+\S+\s+\(/, /\.mjs/, /node:/, /TypeError/, /\/Users\//, /istem hatasi:/];

const DURUMLAR = [
  ["gecerli istek", "/odunc", "Uye U-4711", '{"isbn":"978-0201835953"}', 201],
  ["kimliksiz istek", "/odunc", null, '{"isbn":"978-0201835953"}', 401],
  ["bozuk govde", "/odunc", "Uye U-4711", "{isbn", 400],
  ["eksik alan", "/odunc", "Uye U-4711", "{}", 400],
  ["katalogda yok", "/odunc", "Uye U-4711", '{"isbn":"978-0000000000"}', 404],
  ["raf bos", "/odunc", "Uye U-4711", '{"isbn":"978-0262033848"}', 409],
  ["programci hatasi", "/rapor", "Uye U-4711", null, 500],
];

const cocuk = spawn("node", ["kaynak/http/sunucu.mjs"]);
let kutuk = "";
cocuk.stdout.on("data", (p) => (kutuk += p));
cocuk.stderr.on("data", (p) => (kutuk += p));
await bekle(700);

const kusurlar = [];
console.log(`${"durum".padEnd(6)} | ${"senaryo".padEnd(18)} | yanit govdesi`);
for (const [ad, yol, kimlik, govde, beklenen] of DURUMLAR) {
  const yanit = await fetch(`http://127.0.0.1:8439${yol}`, {
    method: govde === null ? "GET" : "POST",
    headers: { ...(kimlik ? { authorization: kimlik } : {}),
      ...(govde === null ? {} : { "content-type": "application/json" }) },
    body: govde,
  });
  const metin = await yanit.text();
  console.log(`${String(yanit.status).padEnd(6)} | ${ad.padEnd(18)} | ${metin}`);
  if (yanit.status !== beklenen) kusurlar.push(`${ad}: ${beklenen} bekleniyordu, ${yanit.status} geldi`);
  for (const kalip of YASAK) {
    if (kalip.test(metin)) kusurlar.push(`${ad}: yanit govdesinde yasak kalip ${kalip}`);
  }
}

await bekle(200);
cocuk.kill();
await bekle(300);

console.log("--- kutuk ---");
console.log(kutuk.trimEnd().split("\n").map((s) => "  " + s).join("\n"));

if (!/programci_hatasi/.test(kutuk)) kusurlar.push("kutukte programci hatasi kaydi yok");
if (!/TypeError/.test(kutuk)) kusurlar.push("kutukte ic hata turu yok");

console.log("--- denetim ---");
for (const kusur of kusurlar) console.log("  KUSUR  " + kusur);
console.log(kusurlar.length === 0
  ? "  yedi yanit da bicime uygun; ic ayrinti yalniz kutukte"
  : `  ${kusurlar.length} kusur`);
process.exit(kusurlar.length === 0 ? 0 : 1);
```

```
durum  | senaryo            | yanit govdesi
201    | gecerli istek      | {"odunc":{"isbn":"978-0201835953","uye":"U-4711"},"kalan":2}
401    | kimliksiz istek    | {"hata":{"kod":"kimlik_yok","ileti":"Gecerli bir uye kimligi gerekiyor."},"istekKimligi":"i-2"}
400    | bozuk govde        | {"hata":{"kod":"govde_bicimsiz","ileti":"Istek govdesi JSON olarak cozulemedi."},"istekKimligi":"i-3"}
400    | eksik alan         | {"hata":{"kod":"alan_eksik","ileti":"Istek zorunlu alanlari tasimiyor.","ayrintilar":{"eksikAlanlar":["isbn"]}},"istekKimligi":"i-4"}
404    | katalogda yok      | {"hata":{"kod":"kitap_yok","ileti":"Katalogda bu ISBN bulunmuyor.","ayrintilar":{"isbn":"978-0000000000"}},"istekKimligi":"i-5"}
409    | raf bos            | {"hata":{"kod":"raf_bos","ileti":"Kitabin rafta kopyasi kalmadi.","ayrintilar":{"isbn":"978-0262033848"}},"istekKimligi":"i-6"}
500    | programci hatasi   | {"hata":{"kod":"ic_hata","ileti":"Istek islenemedi."},"istekKimligi":"i-7"}
--- kutuk ---
  {"olay":"acildi","baglantiNoktasi":8439}
  {"duzey":"uyari","istekKimligi":"i-2","tur":"istem_hatasi","ad":"IstemHatasi","ileti":"istem hatasi: kimlik_yok","yigit":null}
  {"duzey":"uyari","istekKimligi":"i-3","tur":"istem_hatasi","ad":"IstemHatasi","ileti":"istem hatasi: govde_bicimsiz","yigit":null}
  {"duzey":"uyari","istekKimligi":"i-4","tur":"istem_hatasi","ad":"IstemHatasi","ileti":"istem hatasi: alan_eksik","yigit":null}
  {"duzey":"uyari","istekKimligi":"i-5","tur":"istem_hatasi","ad":"IstemHatasi","ileti":"istem hatasi: kitap_yok","yigit":null}
  {"duzey":"uyari","istekKimligi":"i-6","tur":"istem_hatasi","ad":"IstemHatasi","ileti":"istem hatasi: raf_bos","yigit":null}
  {"duzey":"hata","istekKimligi":"i-7","tur":"programci_hatasi","ad":"TypeError","ileti":"KATALOG.gec is not a function","yigit":"at Server.<anonymous> (sunucu.mjs:44:61)"}
--- denetim ---
  yedi yanit da bicime uygun; ic ayrinti yalniz kutukte
```

Sınama sıfır çıkış kodu üretir. Yığıt izindeki satır ve sütun numarası kodun her
değişikliğinde kayar.

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

**Altı hata yanıtı aynı biçimdedir.** Hepsinde `hata.kod`, `hata.ileti` ve `istekKimligi`
vardır; ikisinde ek olarak `ayrintilar` bulunur. İstemci tarafında tek bir çözümleyici bütün
hataları işleyebilir ve dallanmayı `kod` alanına dayandırır. İletinin kendisi görüntülemek
içindir; karar `kod` ile verilir, çünkü ileti değişebilir, kod değişmez.

**Ayrıntılar yalnız yararlı olduğunda vardır.** `alan_eksik` yanıtı eksik alanların adını
verir, `kitap_yok` yanıtı sorgulanan ISBN'i döndürür. Bunlar istemcinin zaten bildiği ya da
düzeltmesi gereken değerlerdir. Kimlik hatası yanıtında hiçbir ayrıntı yoktur; hangi kimliğin
neden geçersiz olduğunu söylemek, kimlik denemesi yapan bir tarafa geri bildirim vermek
demektir.

**Beşinci senaryo bir ayrımı görünür kılar.** Katalogda bulunmayan ISBN `404`, rafta kopyası
kalmayan kitap `409` alır. İkisi de "veremiyorum" der, ama biri kaynağın hiç olmadığını,
diğeri kaynağın var olup şu an uygun olmadığını bildirir. İstemci ilkinde girdiyi düzeltir,
ikincisinde bekler.

**Yedinci senaryo ayrımın asıl noktasıdır.** İstemciye giden gövde yalnız `ic_hata` kodunu,
sabit tablodan gelen iletiyi ve `i-7` kimliğini taşır. Kütükte ise aynı isteğin `TypeError` olduğu,
iletisinin `KATALOG.gec is not a function` olduğu ve yığıt izinin ilk satırı bulunur. Aynı
olay iki yerde iki farklı ayrıntı düzeyiyle kayıtlıdır ve `i-7` kimliğiyle eşleşir.

**Sınama iki yönlüdür.** Yanıtlarda yasaklı kalıp bulunmaması tek başına yetmez; kütükte
`programci_hatasi` ve `TypeError` kayıtlarının bulunması da sınanır. Hataları sessizce yutan
bir uygulama ilk koşulu geçer, ikincisinde kalır.

## Özet

- Hatalar iki sınıfa ayrılır: istem hatası uygulamanın öngördüğü bir durumdur ve `4xx`
  üretir, programcı hatası uygulamanın kusurudur ve `5xx` üretir.
- İstemciye giden ileti hata nesnesinden değil, sabit bir tablodan gelir; tabloda karşılığı
  olmayan her hata `ic_hata` sayılır, yani varsayılan sızdırmama yönündedir.
- İstemciye gösterilebilir alanlar `ayrintilar` başlığı altında açıkça işaretlenir; kimlik
  hatasında ayrıntı verilmez.
- Bütün işleyiciler hata fırlatır, yanıtı tek bir katman yazar; kütük kaydı istem hatalarını
  `uyari`, programcı hatalarını `hata` düzeyinde tutar ve yığıt izini yalnız ikincisine ekler.
- Sınama yedi senaryonun yanıt gövdesinde yasaklı kalıp aramakla kalmaz, iç ayrıntının kütükte
  bulunduğunu da doğrular; iki yön birlikte sınandığında hataları yutan bir uygulama sınamayı
  geçemez.

## Sonraki Adım

Bu bölümde kurulan parçalar — katmanlı dizin düzeni, ortamdan okunan ve açılışta doğrulanan
yapılandırma, gizli değerlerin ayrılması, standart çıktıya yazılan yapılandırılmış kütük ve
ortak hata yanıtı — birbirinden bağımsız kararlar gibi görünür. Aralarında bir bağ vardır ve
bu bağ tek bir hedefte toplanır: uygulamanın çalıştığı makineden bağımsız olması. Sonraki ders
bu kararları adlandırılmış bir ilkeler kümesi olarak toplar, her ilkenin bu bölümde hangi
ölçümle karşılandığını gösterir ve karşılanmayanları işaret eder.
