HTTP Durum Kodları: 200, 404 ve 500'ün Ötesinde Ne Anlatırlar?
HTTP durum kodlarının beş ailesini; 401/403, 409/422, 429 ve 502/504 gibi sık karıştırılan kodları gerçek API örnekleriyle öğrenin.
HTTP Durum Kodları: Sunucu İsteğe Ne Cevap Veriyor?
Bir API isteği attığımda ilk baktığım şeylerden biri response.status. Çünkü aynı “işlem olmadı” mesajının arkasında bambaşka durumlar olabilir: Kullanıcı giriş yapmamıştır, erişim izni yoktur, kayıt bulunamamıştır veya sunucu isteği tamamlayamamıştır. Arayüzün ne göstereceğini de bu ayrım belirler.
HTTP durum kodu, sunucunun bir isteğe verdiği yanıtın sonucunu üç basamaklı bir sayıyla anlatır. HTTP/1.1 yanıtında örneğin HTTP/1.1 201 Created satırını görürsün. HTTP/2 ve HTTP/3'te kodun taşınma biçimi farklıdır; kodun anlamı ise aynıdır.
📌 30 Saniyelik Özet
- 1xx: İşlem sürüyor; ara bilgi.
- 2xx: İstek başarılı.
- 3xx: Yönlendirme veya önbellekle ilgili bir işlem gerekiyor.
- 4xx: İstek bu hâliyle tamamlanamıyor.
- 5xx: Sunucu isteği tamamlayamadı.
Buradaki önemli ayrıntı şu: 4xx her zaman “kullanıcı hata yaptı”, 5xx de her zaman “backend kodu bozuk” anlamına gelmez. Örneğin 429 hız sınırına takıldığını, 503 ise hizmetin geçici olarak kullanılamadığını gösterebilir.
🔄 1xx: İşlem Sürerken Gelen Ara Yanıtlar
100 Continue, uygun bir istekte istemcinin gövdeyi göndermeye devam edebileceğini bildirir. 101 Switching Protocols, bir protokol yükseltmesinin kabul edildiğini gösterir. 103 Early Hints ise nihai yanıt hazırlanırken tarayıcıya bazı kaynaklar için erken ipuçları verebilir.
Bu kodlar çoğunlukla uygulama içinde işlediğin nihai API sonuçları değildir. Özellikle 103 yanıtının ardından asıl yanıt gelir.
✅ 2xx: Başarılı İsteklerin Hepsi 200 Değildir
200 OK
İstek başarılıdır. Bir ürünü listelemek veya bir kaydın detayını döndürmek için sık kullanılır.
201 Created
İstek sonucunda yeni bir kaynak oluşturulmuştur. Örneğin POST /api/products ile ürün ekledin. Yanıttaki Location başlığı yeni kaynağın adresini gösterebilir.
HTTP/1.1 201 Created
Location: /api/products/42
Content-Type: application/json
{ "id": 42, "name": "Yeni ürün" }
202 Accepted
İstek kabul edilmiştir fakat işlem henüz tamamlanmamıştır. Arka planda rapor üretmek buna güzel bir örnek. 202, raporun başarıyla üretileceğini garanti etmez; işlem durumunu sorgulayabileceğin bir adres döndürmek yararlıdır.
HTTP/1.1 202 Accepted
Content-Type: application/json
{
"jobId": "report-42",
"statusUrl": "/api/jobs/report-42"
}
204 No Content
İşlem başarılıdır fakat yanıt gövdesi yoktur. Bir silme işlemi başarılı olduğunda kullanılabilir. Frontend tarafında bu yanıt için koşulsuz response.json() çağırırsan hata alabilirsin.
const response = await fetch("/api/products/42", {
method: "DELETE",
});
if (!response.ok) {
throw new Error("Ürün silinemedi");
}
const data =
response.status === 204 ? null : await response.json();
206 Partial Content ise istemcinin Range isteğine karşılık verinin yalnızca belirli bir bölümünün döndüğünü gösterir. Video oynatma ve büyük dosya indirme işlemlerinde karşına çıkabilir.
↪️ 3xx: Yönlendirme ve Önbellek
301 Moved Permanently kalıcı, 302 Found geçici taşınmayı bildirir. Ancak özellikle POST isteklerinde önemli bir ayrıntı vardır: 301 ve 302 yönlendirmelerinde istemci, yöntemi GET'e dönüştürebilir.
İsteğin HTTP yönteminin korunması gerekiyorsa 307 Temporary Redirect veya 308 Permanent Redirect kullanılır. 303 See Other ise bir işlemden sonra sonucu başka bir adresten GET ile göstermek için uygundur.
🎯 Hangisini Seçelim?
- Geçici taşınma, yöntem korunsun:
307 - Kalıcı taşınma, yöntem korunsun:
308 - POST sonrası başka sayfayı GET ile aç:
303
304 Not Modified ise klasik bir yönlendirme gibi çalışmaz. Kaynak, istemcinin elindeki sürümden beri değişmediyse önbellekteki kopya kullanılabilir. Genellikle ETag ve If-None-Match ile birlikte görülür. 304 yanıtında gövde bulunmaz.
⚠️ 4xx: İstemci Hangi Adımı Atmalı?
Frontend tarafında en çok dikkat ettiğim aile burası. Çünkü her 4xx yanıtında aynı “Bir hata oluştu” mesajını göstermek kullanıcıya hiçbir şey anlatmaz.
400, 415 ve 422
- 400 Bad Request: İstek, biçimi veya içeriği nedeniyle işlenemiyor.
- 415 Unsupported Media Type: Gönderilen içerik türü desteklenmiyor.
- 422 Unprocessable Content: İçerik türü ve sözdizimi anlaşılmış olsa da içerdiği talimat işlenemiyor.
Örneğin API geçerli bir JSON alıyor fakat e-posta alanındaki değeri kabul edemiyorsa 422 kullanabilir. Yine de her alan doğrulama hatasında mutlaka 422 dönülecek diye bir zorunluluk yoktur. Burada asıl önemli nokta API sözleşmesinin tutarlı olmasıdır.
POST /api/users
Content-Type: application/json
{ "email": "gecersiz-adres" }
HTTP/1.1 422 Unprocessable Content
Content-Type: application/problem+json
{
"type": "https://example.com/problems/invalid-email",
"title": "E-posta geçersiz",
"status": 422,
"detail": "Geçerli bir e-posta adresi girin."
}
Örnekteki application/problem+json, HTTP API hatalarını açıklamak için standartlaştırılmış biçimlerden biridir.
🔐 401 Unauthorized ve 403 Forbidden Farkı
İsimleri yüzünden en çok karıştırılan iki kod bunlar. 401, korunan kaynağa erişmek için geçerli kimlik doğrulama bilgisinin sağlanmadığını anlatır. HTTP standardına uygun 401 yanıtında ilgili WWW-Authenticate başlığı bulunur.
403 ise sunucunun isteği anladığını fakat işlemi reddettiğini gösterir. Kullanıcı giriş yapmış olabilir; ancak bu kaynağa erişim yetkisi olmayabilir.
🧩 Arayüzde Karşılığı
- 401: Kullanıcıya giriş akışını sun.
- 403: “Bu işlem için yetkiniz yok” mesajını göster.
Bazı sistemler, bir kaynağın varlığını açığa çıkarmamak için 403 yerine 404 dönebilir.
🔎 404 Not Found ve 405 Method Not Allowed
404, istenen kaynağın bulunmadığını bildirir. Kaynağın kalıcı olarak silindiğini tek başına söylemez; bu durum biliniyorsa 410 Gone daha açıklayıcı olabilir.
405 ise kaynak mevcut olduğu hâlde kullanılan HTTP yöntemine izin verilmediğini gösterir. Örneğin yalnızca GET destekleyen bir adrese DELETE göndermek. 405 yanıtındaki Allow başlığı desteklenen yöntemleri belirtmelidir.
⚔️ 409 Conflict ve 422 Arasındaki Fark
409 Conflict, isteğin kaynağın mevcut durumuyla çatıştığını anlatır. İki kullanıcının aynı kaydı düzenlediğini düşün: Birinci kullanıcı değişikliği kaydetti, ikinci kullanıcı ise eski sürüm üzerinden kaydetmeye çalışıyor. Bu bir durum çatışmasıdır.
422 ise biçimi anlaşılmış içeriğin işlenememesine odaklanır. İş kurallarının sınırlarında iki kodun kullanımı API tasarımına göre değişebilir. Önemli olan istemciye ne olduğunu açıkça söylemektir.
⏱️ 429 Too Many Requests
Kısa sürede izin verilenden fazla istek gönderdiğinde 429 alabilirsin. Sunucu, ne zaman yeniden deneyebileceğini Retry-After başlığıyla belirtebilir.
HTTP/1.1 429 Too Many Requests
Retry-After: 60
{
"message": "Lütfen bir dakika sonra tekrar deneyin."
}
İstemci burada bekleyip yeniden deneyebilir. Ancak özellikle POST gibi yan etkili istekleri, önceki işlemin sonucunu bilmeden körlemesine tekrarlamak doğru değildir.
Karşına çıkabilecek diğer kodlardan bazıları: 408 istek zaman aşımı, 412 ön koşulun sağlanmaması, 413 içeriğin çok büyük olması, 416 istenen aralığın karşılanamaması ve 451 yasal nedenlerle erişim sağlanamaması.
🛠️ 5xx: Sunucu Yanıtı Tamamlayamadı
- 500 Internal Server Error: Daha özel bir kodun uygun olmadığı beklenmedik sunucu hatası.
- 501 Not Implemented: Sunucu isteği yerine getirmek için gereken işlevi desteklemiyor.
- 502 Bad Gateway: Proxy veya ağ geçidi, üst sunucudan geçersiz bir yanıt aldı.
- 503 Service Unavailable: Hizmet geçici olarak kullanılamıyor.
- 504 Gateway Timeout: Proxy veya ağ geçidi, üst sunucudan zamanında yanıt alamadı.
Özellikle 502 ve 504 ayrımı hata ayıklamada işe yarar: Birinde üst sunucudan geçersiz yanıt gelir, diğerinde beklenen yanıt zamanında gelmez. Bir dashboard isteğinde 504 gördüğümde yalnızca frontend'i değil, proxy ile arkasındaki servis arasındaki süreyi de incelerim.
503 bakım veya aşırı yük gibi geçici durumlarda kullanılabilir. Sunucu uygun görürse bu yanıtta da Retry-After başlığı gönderebilir.
💻 Frontend'de Durum Kodlarını Nasıl Ele Alırım?
fetch, sunucudan 404 veya 500 geldi diye otomatik olarak promise'ı reddetmez. HTTP sonucunu response.ok veya response.status ile kontrol etmelisin. Ağ bağlantısının başarısız olması ise ayrı bir durumdur.
async function loadProduct(id) {
try {
const response = await fetch(
"/api/products/" + encodeURIComponent(id)
);
if (response.status === 401) {
return { kind: "login-required" };
}
if (response.status === 403) {
return { kind: "forbidden" };
}
if (response.status === 404) {
return { kind: "not-found" };
}
if (response.status === 429) {
return { kind: "rate-limited" };
}
if (!response.ok) {
return { kind: "server-error" };
}
return {
kind: "success",
product: await response.json(),
};
} catch {
return { kind: "network-error" };
}
}
Bu örnek temel ayrımı gösteriyor. Gerçek projede hata gövdesini, iptal edilen istekleri, yeniden denemeyi ve kimlik doğrulama akışını ayrıca ele almak gerekir. 204 geldiğinde gövde okumamayı da unutma.
📚 Eski Durum Kodu Listelerine Neden Dikkat Etmeli?
Bu yazıya kaynak olan eski listede HTTP/1.1 için RFC 2616 temel alınmış. Güncel HTTP semantiği için ana kaynak RFC 9110. Bu yüzden eski listelerde 308 hâlâ “deneysel” veya 422 yalnızca “WebDAV” olarak geçebiliyor. Güncel adları sırasıyla Permanent Redirect ve Unprocessable Content.
418 I'm a teapot esprili geçmişiyle bilinir; üretim API'sinde genel hata kodu olarak seçilmez. 420, 444 veya 499 gibi kodlar da belirli servislerde görülebilir. Bunları bütün HTTP istemcilerinin aynı şekilde yorumlayacağı genel bir API sözleşmesi olarak düşünmemek gerekir.
💡 Sonuç
İyi bir API yanıtı yalnızca “başarılı” veya “başarısız” demez; istemcinin sonraki adımını anlaşılır kılar. Yeni kayıt için 201, gövdesiz başarı için 204, yetki veya doğrulama durumları için uygun 4xx, geçici hizmet sorunları için uygun 5xx seçmek hem kullanıcı deneyimini hem de hata ayıklamayı kolaylaştırır.
Durum kodunu seçtikten sonra iş bitmez. Yanıt gövdesinde anlaşılır bir açıklama sunmak ve aynı duruma uygulamanın her yerinde aynı şekilde cevap vermek de API sözleşmesinin parçasıdır.