Mert Özen Her Satırda Daha İleri
Backend .NET Core Web API — #01

ASP.NET Core Web API Nedir? HTTP ve REST Temelleri

Mert Özen 4 Kas 2026 9 dk 2 görüntülenme
ASP.NET Core Web API Nedir? HTTP ve REST Temelleri

Web API'yi HTTP penceresinden tanıyoruz: istek ve response anatomisi, method'lar ve idempotency, status code aileleri, kaynak odaklı URL tasarımı. Serinin .NET 6 kemik yapı ve sürüm yazıları kurgusunu anlatıyoruz.

Sürüm notu: Bu yazıdaki kodlar .NET 6 ile yazılmıştır ve .NET 10'da da değişmeden çalışır.

Önce Tahmin Et

Bir ürünü silmek için API'ye DELETE /api/products/3 isteği gönderiyorsun. Ağ bir an takıldı, istemci emin olamadı ve aynı isteği bir kez daha yolladı. İki istekte de aynı status code'u mu alırsın? Bir de şu: farklı cevaplar geliyorsa DELETE'e hâlâ "idempotent" diyebilir miyiz? Cevabı aklında tut, birkaç bölüm sonra gerçek bir uygulamanın verdiği yanıtla karşılaştıracağız.

Web API Dediğimiz Şey

Web API, başka programların HTTP üzerinden konuştuğu bir uygulamadır. Karşısında bir insan yok; bir mobil uygulama, bir Angular arayüzü, başka bir backend service ya da bir terminaldeki curl var. Bu istemciler bir istek gönderir, API de veriyle, çoğu zaman JSON ile cevap verir. Ekranda bir HTML sayfası çizmek API'nin işi değil; o işi istemci yapar.

Bir restoran düşün. Mutfak senin iş kuralların ve veritabanın, masadaki müşteri de istemci. Müşteri mutfağa girip tencereyi karıştırmaz. Garsona menüdeki dille sipariş verir, garson da siparişi mutfağa iletip tabağı geri getirir. Web API o garsondur. Menü ise API'nin sözleşmesi: hangi adrese, hangi method ile, hangi veriyi gönderirsen ne alacağını tarif eder.

Bu seride baştan sona tek bir örnek üzerinden ilerleyeceğiz: ürünleri, kategorileri ve siparişleri yöneten küçük bir ProductCatalog API. Bu yazıdaki bütün yanıtlar o uygulamanın çalışan bir sürümünden alındı. Kodunu ilerleyen yazılarda adım adım birlikte yazacağız; bugün sadece dışarıdan, HTTP penceresinden bakıyoruz.

Bir HTTP İsteğinin Anatomisi

HTTP düz metin bir protokoldür. Bir istek üç parçadan oluşur: hangi kaynağa ne yapmak istediğini söyleyen ilk satır, istekle ilgili ek bilgileri taşıyan header'lar ve gerekiyorsa veriyi taşıyan body. Yeni bir ürün eklemek isteyen bir istemcinin gönderdiği istek kabaca şöyle görünür:

POST /api/products HTTP/1.1
Host: localhost:5214
Content-Type: application/json
Accept: application/json

{"name":"Laptop Stand","price":640,"categoryId":2}

İlk satırda method (POST), adres (/api/products) ve protokol sürümü var. Content-Type header'ı "sana gönderdiğim body JSON" der, Accept ise "ben de cevabı JSON olarak istiyorum". Header'lar ile body arasındaki boş satır zorunludur; sunucu body'nin nerede başladığını o boşluktan anlar.

Response da aynı iskeleti taşır, sadece ilk satırda method yerine status code bulunur. curl -i ile response'un header'larını da görebilirsin:

$ curl -i http://localhost:5214/api/products
HTTP/1.1 200 OK
Content-Type: application/json; charset=utf-8
Date: Tue, 03 Nov 2026 21:18:27 GMT
Server: Kestrel
Transfer-Encoding: chunked

[{"id":1,"name":"Mechanical Keyboard","price":1450,"categoryId":1},{"id":2,"name":"Wireless Mouse","price":520,"categoryId":1},{"id":3,"name":"USB-C Hub","price":890,"categoryId":2}]

Server: Kestrel satırına dikkat et. Kestrel, ASP.NET Core'un yerleşik web sunucusu; isteği soketten okuyan ve uygulamana teslim eden o. Body'deki property adlarının name, price gibi küçük harfle başladığını da gör. C# tarafında bunlar Name ve Price; ASP.NET Core, System.Text.Json ile serileştirirken varsayılan olarak camelCase kullanır. JavaScript dünyasının alıştığı biçim bu olduğu için istemci tarafında ekstra bir dönüşüm gerekmez.

Şimdi o POST isteğini gerçekten gönderelim:

$ curl -i -X POST http://localhost:5214/api/products -H "Content-Type: application/json" -d '{"name":"Laptop Stand","price":640,"categoryId":2}'
HTTP/1.1 201 Created
Content-Type: application/json; charset=utf-8
Date: Tue, 03 Nov 2026 21:18:27 GMT
Server: Kestrel
Location: http://localhost:5214/api/Products/4
Transfer-Encoding: chunked

{"id":4,"name":"Laptop Stand","price":640,"categoryId":2}

Cevap 200 değil, 201 Created. Ayrıca Location header'ı yeni kaynağın adresini veriyor. İyi bir API "oldu" demekle yetinmez, oluşturduğu şeyin nerede durduğunu da söyler.

HTTP Method'ları ve Idempotency

Method, kaynağa ne yapmak istediğini anlatan fiildir. Bir Web API'de en sık beşini kullanırsın: GET okur, POST yeni bir kaynak oluşturur, PUT var olan kaynağı baştan sona yenisiyle değiştirir, PATCH sadece bir kısmını günceller, DELETE siler.

Bu method'ları iki özellik üzerinden düşünmek işini çok kolaylaştırır. Safe bir method sunucudaki durumu değiştirmez; GET safe'tir, onu bin kez çağırsan veri yerinden oynamaz. Idempotent bir method ise bir kez de çağırsan on kez de çağırsan sunucuda aynı sonucu bırakır. Ölçüt cevap değil, sunucuda kalan izdir. Şimdi her method'u art arda ikişer kez çağıralım:

$ curl -s -o /dev/null -w "%{http_code}\n" -X DELETE http://localhost:5214/api/products/3
204
$ curl -s -o /dev/null -w "%{http_code}\n" -X DELETE http://localhost:5214/api/products/3
404
$ curl -s -o /dev/null -w "%{http_code}\n" -X PUT http://localhost:5214/api/products/2 -H "Content-Type: application/json" -d '{"name":"Wireless Mouse","price":560,"categoryId":1}'
204
$ curl -s -o /dev/null -w "%{http_code}\n" -X PUT http://localhost:5214/api/products/2 -H "Content-Type: application/json" -d '{"name":"Wireless Mouse","price":560,"categoryId":1}'
204
$ curl -s -o /dev/null -w "%{http_code}\n" -X POST http://localhost:5214/api/products -H "Content-Type: application/json" -d '{"name":"Webcam","price":1100,"categoryId":2}'
201
$ curl -s -o /dev/null -w "%{http_code}\n" -X POST http://localhost:5214/api/products -H "Content-Type: application/json" -d '{"name":"Webcam","price":1100,"categoryId":2}'
201

Tahminin için cevap burada. İlk DELETE 204 No Content döndü, ikincisi 404 Not Found. Cevaplar farklı, ama sunucudaki durum aynı: 3 numaralı ürün iki istekten sonra da yok. Yani DELETE idempotent. Idempotency "her seferinde aynı cevabı alırsın" demek değil, "isteği tekrarlamak sunucuya ek bir etki bırakmaz" demek.

PUT da idempotent: ürünü iki kez aynı hâline getirdik, sonuç değişmedi. POST ise değil. Aynı isteği iki kez gönderdik ve iki ayrı Webcam kaydı oluştu. Ağ takılması yüzünden tekrar edilen bir sipariş POST'unun neden iki sipariş açtığını artık biliyorsun.

PATCH'in durumu ise tasarımına bağlı. Örnek API'de PATCH /api/products/1/price?amount=50 fiyatı 50 artırıyor. Bunu iki kez çağırdığında iki kez de 204 aldık, ama fiyat 1450'den 1550'ye çıktı. "Fiyatı 1500 yap" diyen bir PATCH idempotent olurdu, "fiyatı 50 artır" diyen olmaz.

Status Code Aileleri

Status code'u ezberlemek yerine ilk rakamına bakmayı alışkanlık edin. Beş aile var: 1xx bilgilendirme, 2xx başarı, 3xx yönlendirme, 4xx istemci hatası, 5xx sunucu hatası. 4 ile başlıyorsa "sen yanlış bir şey istedin", 5 ile başlıyorsa "ben bir şeyi beceremedim" demektir. İstemci geliştiricisi bu ayrıma göre davranır: 4xx'te kullanıcıya mesaj gösterir, 5xx'te isteği bir süre sonra tekrar dener.

Bir API'de en çok göreceklerin: 200 OK, 201 Created, 204 No Content, 400 Bad Request, 401 Unauthorized, 403 Forbidden, 404 Not Found, 409 Conflict ve 500 Internal Server Error. Daha az bilinen ama çok öğretici bir tanesine bakalım. Tüm ürün listesini silmeye çalışırsak ne olur?

$ curl -i -X DELETE http://localhost:5214/api/products
HTTP/1.1 405 Method Not Allowed
Content-Length: 0
Date: Tue, 03 Nov 2026 21:18:28 GMT
Server: Kestrel
Allow: GET, POST

/api/products adresi var ama DELETE kabul etmiyor. Bu yüzden 404 değil 405 döndü ve Allow header'ı o adreste hangi method'ların geçerli olduğunu söyledi. Bunu ASP.NET Core bizim yerimize yaptı, biz tek satır kod yazmadık. Status code'ları hangi durumda hangisini döneceğinle birlikte ayrı bir yazıda detaylandıracağız.

Kaynak Odaklı URL Tasarımı

REST'in en pratik fikri şu: URL bir kaynağı gösterir, method da o kaynağa ne yapılacağını söyler. Fiil method'da durduğu için URL'de fiile ihtiyacın kalmaz. ProductCatalog API'nin adresleri bu yüzden şöyle:

GET    /api/products
GET    /api/products/42
POST   /api/products
PUT    /api/products/42
PATCH  /api/products/42/price
DELETE /api/products/42
GET    /api/categories/2/products

Kaynak adları çoğul ve isim: products, categories. Tek bir kaynağa id ile inersin. Bir kategoriye ait ürünler gibi doğal bir hiyerarşi varsa bunu yol ile ifade edersin. Filtreleme, sıralama ve sayfalama gibi "aynı listeye farklı bakış" isteyen şeyler ise query string'e gider: /api/products?categoryId=2&sort=price.

Sahada sık gördüğün ama kaçınman gereken biçimler de şunlar:

GET  /api/getProducts
POST /api/products/delete/42
GET  /api/product-list?action=create

Üçünde de fiil URL'ye kaçmış. İkincisi daha da tehlikeli: silme işi POST ile yapıldığı için ne cache'ler ne proxy'ler ne de istemci kütüphaneleri bu isteğin ne yaptığını method'a bakarak anlayabilir. Birinci ve üçüncü örnek ise aynı kaynak için her geliştiricinin kendi sözlüğünü uydurmasına yol açar.

Neden ASP.NET Core

ASP.NET Core, Microsoft'un açık kaynak ve cross-platform web framework'ü. Aynı kod Windows'ta, Linux'ta ve macOS'ta çalışır; container içinde Linux üzerinde çalıştırmak bugün en yaygın senaryo. Yerleşik Kestrel sunucusu sayesinde önüne IIS koymak zorunda değilsin.

Bir API'nin ihtiyaç duyduğu temel parçalar kutudan çıkar: dependency injection, configuration, logging, routing, model binding, validation, authentication ve authorization. Bunlar framework'ün içinde birbirini tanıyan parçalar olarak gelir; ayrı ayrı library seçip birbirine yapıştırmak zorunda kalmazsın. Endpoint yazmak için de iki yol var: controller'lar ve minimal API'ler. Bu seride ikisini de göreceğiz, ağırlık kurumsal projelerde hâlâ baskın olan controller'larda olacak.

Bu Serinin Kurgusu

Seri iki bloktan oluşuyor. İlk blok kemik yapı: ASP.NET Core Web API'nin temel kavramlarını .NET 6 API yüzeyiyle anlatıyoruz. Bu blokta yazdığımız her örnek .NET 6'da derlenir ve .NET 10'da da değişmeden çalışır. Bunu gerçekten ikisinde de derleyip çalıştırarak doğruluyoruz.

Neden .NET 6? 8 Kasım 2021'de çıkan .NET 6, tek dosyalık Program.cs ile minimal hosting modelini getirdi. O günden .NET 10'a kadar uygulamanın iskeleti aynı kaldı; sonraki sürümler bu iskelete yeni parçalar ekledi. .NET 6'nın desteği 12 Kasım 2024'te bitti, ama sahada .NET 6 ile yazılmış çok proje var ve onları okuyup taşıyabilmek ayrı bir beceri. Önce sürümden bağımsız temeli oturtursan, sonraki sürümlerin getirdiklerini çok daha kolay yerine koyarsın.

İkinci blok sürümlerle evrim. Önce eski projeleri okumak için .NET Core 3.1 ve .NET 5'teki Startup.cs dünyasına bakacağız. Ardından .NET 7, 8, 9 ve 10 için birer yazı gelecek. Her birinde ".NET 6'da bunu böyle yapıyorduk, şimdi şöyle" diye karşılaştırıp kısa bir sürüme geçiş notu vereceğiz. Kemik yapı yazılarında da sonradan gelen bir özellik konuyla ilgiliyse tek cümleyle işaret edeceğiz.

Takvime de bakalım. .NET 8 ve .NET 9'un desteği 10 Kasım 2026'da birlikte bitiyor. .NET 10 LTS sürümü ve 14 Kasım 2028'e kadar destekleniyor. .NET 11'in de Kasım 2026'da çıkması planlanıyor; çıktığında seriye ayrı bir yazı olarak eklenecek.

Sık Düşülen Tuzaklar

Her şeye 200 OK dönmek. Hata olduğunda body'ye "success": false yazıp status code'u 200 bırakan API'ler hâlâ çok yaygın. Bu durumda istemci her cevabı açıp içine bakmak zorunda kalır; monitoring araçları da hatayı göremez. Status code'u gerçek durumu anlatsın.

GET ile veri değiştirmek. GET /api/products/42/delete gibi bir adres, bir tarayıcı ön yüklemesi ya da bir arama motoru botu tarafından tetiklendiğinde veri sessizce silinir. GET safe kalmalı.

POST'u güvenle tekrar edilebilir sanmak. Ödeme ya da sipariş gibi işlemlerde istemcinin aynı POST'u tekrar göndermesi çift kayıt demektir. Bunu tasarım aşamasında düşünmen gerekir.

404 ile 405'i karıştırmak. Adres yoksa 404, adres var ama method yanlışsa 405 döner. Hata ayıklarken bu ayrım sana yarım saat kazandırır.

Küçük Bir Deneme

★☆☆ Terminalden herhangi bir public API'ye (ör. GitHub'ın https://api.github.com/users/octocat adresi) curl -i ile istek at. Status code'u, Content-Type header'ını ve body'deki property adlarının biçimini not et.

★★☆ Bir kütüphane uygulaması için kaynak odaklı URL'ler tasarla: kitaplar, yazarlar, bir yazarın kitapları, bir kitabın ödünç alınması ve iadesi. Ödünç alma işini fiilsiz nasıl ifade edeceğini düşün.

★★★ Aşağıdaki işlemlerin her biri için hangi method'u seçeceğini ve o method'un idempotent olup olmadığını yaz: sepete ürün eklemek, sepetteki ürün adedini 3 yapmak, adedi bir artırmak, sepeti boşaltmak.

Bu yanıtları üreten uygulamayı kendi makinende ayağa kaldırmak için önce bir ortama ihtiyacın var. Bir sonraki yazıda .NET SDK'yı kuruyor, birden fazla SDK'yı global.json ile yönetiyor ve dotnet new webapi ile ilk projeyi oluşturup ilk isteğimizi atıyoruz.