Bir REST API geliştirmek, yalnızca veriyi JSON formatında döndüren URL’ler oluşturmaktan ibaret değildir. Kaynakların nasıl modellendiği, endpoint’lerin nasıl isimlendirildiği ve her işlemde hangi HTTP durum kodunun döndürüldüğü API’nin anlaşılabilirliğini doğrudan etkiler.
Tutarsız tasarlanan bir API, istemci tarafındaki kodu karmaşıklaştırır. Aynı tür işlemler için farklı isimlendirmelerin kullanılması, hataların her endpoint’te başka biçimde dönmesi ve yanlış durum kodları entegrasyon maliyetini artırır.
İyi tasarlanmış bir REST API ise geliştiricinin endpoint’in davranışını dokümantasyonu açmadan büyük ölçüde tahmin edebilmesini sağlar.
Bu yazıda REST API tasarımında kaynak, endpoint ve durum kodu seçimini uygulanabilir örneklerle ele alacağız.
REST API nedir?
REST, “Representational State Transfer” ifadesinin kısaltmasıdır. Dağıtık sistemler için tanımlanan bir yazılım mimarisi yaklaşımıdır.
REST yaklaşımında uygulama verileri kaynaklar üzerinden modellenir. İstemci, bu kaynaklarla HTTP aracılığıyla iletişim kurar.
Örneğin bir e-ticaret sisteminde şu kavramlar kaynak olarak ele alınabilir:
- Kullanıcı
- Ürün
- Kategori
- Sipariş
- Ödeme
- Adres
- Yorum
Her kaynak bir URI ile temsil edilir:
/users
/products
/ordersİstemcinin kaynak üzerinde gerçekleştirmek istediği işlem ise HTTP metoduyla ifade edilir:
GET /products
POST /products
GET /products/42
PATCH /products/42
DELETE /products/42Buradaki temel fikir, işlemi endpoint adına yazmak yerine kaynak ile HTTP metodunu birlikte kullanmaktır.
REST API tasarımında kaynak nedir?
Kaynak, API üzerinden erişilen veya yönetilen iş varlığıdır. Veri tabanındaki her tabloyu doğrudan bir API kaynağına dönüştürmek doğru bir yaklaşım değildir.
API kaynakları, veri tabanı yapısından çok iş alanını ve istemcinin ihtiyaçlarını yansıtmalıdır.
Örneğin veri tabanında sipariş bilgileri birden fazla tabloda tutulabilir:
orders
order_items
order_addresses
order_paymentsAncak istemci tarafında bunların tamamı bir sipariş kaynağının farklı bölümleri olarak sunulabilir:
{
"id": 824,
"status": "preparing",
"items": [
{
"productId": 42,
"quantity": 2
}
],
"deliveryAddress": {
"city": "İstanbul",
"district": "Kadıköy"
},
"totalAmount": 1450
}API sözleşmesinin veri tabanı şemasından bağımsız tutulması, iç sistemde yapılan değişikliklerin istemcileri daha az etkilemesine yardımcı olur.
Doğru kaynak nasıl belirlenir?
Bir kavramın kaynak olup olmadığını değerlendirirken şu sorular sorulabilir:
- İş alanında bağımsız bir anlam taşıyor mu?
- Bir kimliğe sahip mi?
- Oluşturulabilir, görüntülenebilir veya güncellenebilir mi?
- Başka kaynaklarla ilişkisi bulunuyor mu?
- İstemcinin bu kavrama doğrudan erişmesi gerekiyor mu?
Örneğin bir projede görevler bağımsız olarak yönetiliyorsa aşağıdaki endpoint anlamlıdır:
GET /tasks/91Görev yalnızca proje içerisinde anlamlıysa iç içe bir yapı tercih edilebilir:
GET /projects/12/tasks/91Kaynak sınırları belirlenirken yalnızca teknik yapı değil, iş kuralları da dikkate alınmalıdır.
Endpoint isimlendirme nasıl yapılmalıdır?
Endpoint’lerin tutarlı şekilde isimlendirilmesi, API kullanımını önemli ölçüde kolaylaştırır.
Kaynak isimlerinde isim kullanın
Endpoint’ler kaynakları temsil ettiği için fiil yerine isim kullanmak daha uygundur.
Tercih edilmeyen yaklaşım:
GET /getProducts
POST /createProduct
POST /deleteProductDaha tutarlı yaklaşım:
GET /products
POST /products
DELETE /products/42GET, POST ve DELETE metotları işlemi zaten ifade eder. Aynı bilgiyi endpoint adına tekrar yazmak gereksizdir.
Tekil veya çoğul kullanımı tutarlı tutun
Kaynak adlarında tekil veya çoğul kullanım konusunda farklı tercihler bulunabilir. Önemli olan API genelinde aynı yaklaşımın korunmasıdır.
Çoğul kullanım genellikle daha okunaklıdır:
/users
/users/15
/users/15/ordersŞu şekilde karışık kullanım yapılmamalıdır:
/user
/products
/order/24Küçük harf ve kısa çizgi tercih edin
URL’lerde küçük harf kullanmak, büyük-küçük harf kaynaklı karışıklıkları azaltır. Birden fazla kelimeden oluşan adlarda kısa çizgi tercih edilebilir:
/payment-methods
/order-items
/shipping-addressesAşağıdaki gibi farklı biçimlerin aynı API içerisinde karıştırılmaması gerekir:
/paymentMethods
/PaymentMethods
/payment_methodsDosya uzantısı kullanmayın
İçerik türü endpoint uzantısıyla değil, HTTP başlıklarıyla yönetilmelidir.
Tercih edilmeyen kullanım:
GET /products.jsonTercih edilen kullanım:
GET /products
Accept: application/jsonURL yapısını gereksiz yere derinleştirmeyin
Kaynak ilişkileri iç içe endpoint’lerle gösterilebilir:
GET /users/15/ordersAncak çok derin URL’ler API kullanımını zorlaştırır:
GET /companies/4/departments/8/users/15/orders/42/itemsDerin yapı yerine bazı kaynaklar bağımsız olarak sunulabilir:
GET /orders/42/itemsPratikte bir veya iki ilişki seviyesini aşmayan yapılar daha kolay yönetilir.
HTTP metotları nasıl seçilmelidir?
HTTP metotları istemcinin kaynak üzerinde gerçekleştirmek istediği işlemi belirtir.
GET: Kaynağı görüntüleme
Bir kaynak veya kaynak koleksiyonu alınırken kullanılır:
GET /products
GET /products/42GET isteği sunucu tarafında kaynak durumunu değiştirmemelidir.
Liste sonuçları filtreleme, sıralama ve sayfalama parametreleriyle daraltılabilir:
GET /products?category=computer&sort=price&page=2POST: Yeni kaynak veya işlem oluşturma
Yeni bir kaynak oluşturmak için kullanılabilir:
POST /ordersİstek gövdesi:
{
"customerId": 18,
"items": [
{
"productId": 42,
"quantity": 2
}
]
}Kaynak başarıyla oluşturulduğunda sunucu genellikle 201 Created döndürür. Oluşturulan kaynağın adresi Location başlığıyla belirtilebilir:
HTTP/1.1 201 Created
Location: /orders/824POST yalnızca CRUD işlemlerine bağlı değildir. Kaynak üzerinde ayrı bir iş süreci başlatmak için de kullanılabilir:
POST /orders/824/cancellationsBu yaklaşım, iptal işlemini bağımsız bir kaynak veya süreç olarak modeller.
PUT: Kaynağı tamamen güncelleme veya belirli adreste oluşturma
PUT genellikle kaynağın mevcut temsilini tamamen değiştirmek için kullanılır:
PUT /users/15İstemci, kaynağın yeni durumunu tam olarak gönderir. Eksik alanların nasıl ele alınacağı API sözleşmesinde açıkça belirtilmelidir.
PUT idempotent olmalıdır. Aynı istek birden fazla kez gönderildiğinde sistemin nihai durumu değişmemelidir.
PATCH: Kaynağı kısmen güncelleme
Kaynağın yalnızca belirli alanları değiştirilecekse PATCH kullanılabilir:
PATCH /users/15{
"phone": "+90 555 000 00 00"
}PATCH isteğinin veri formatı ve alanların nasıl güncelleneceği dokümantasyonda açıklanmalıdır. JSON Merge Patch veya JSON Patch gibi standart formatlar değerlendirilebilir.
DELETE: Kaynağı silme
Bir kaynağın silinmesi için kullanılır:
DELETE /products/42Silme işlemi tamamlandığında gövdesiz 204 No Content yanıtı verilebilir.
Uygulama kaydı fiziksel olarak silmek yerine pasif duruma getiriyorsa API davranışı istemci açısından açık olmalıdır.
Idempotency neden önemlidir?
Bir işlemin aynı istek tekrarlandığında sistem üzerinde ek bir değişiklik oluşturmaması idempotency olarak adlandırılır.
GET, PUT ve DELETE metotlarının idempotent davranması beklenir.
Örneğin aşağıdaki isteğin iki kez gönderilmesi, kullanıcının durumunu iki farklı sonuca götürmemelidir:
PUT /users/15/status{
"status": "active"
}POST işlemleri doğal olarak idempotent olmak zorunda değildir. Ödeme veya sipariş oluşturma gibi hassas işlemlerde ağ hatası nedeniyle tekrar gönderilen POST istekleri çift kayıt oluşturabilir.
Bu durumlarda istemciden bir idempotency anahtarı alınabilir:
Idempotency-Key: 67e55044-10b1-426f-9247-bb680e5fe0c8Sunucu aynı anahtarla gelen tekrar isteğini yeni bir işlem olarak değerlendirmek yerine önceki sonucu döndürebilir.
HTTP durum kodu seçimi neden önemlidir?
HTTP durum kodları, isteğin sonucunu istemciye standart bir biçimde bildirir.
Her yanıtta 200 OK döndürüp gerçek sonucu gövde içinde belirtmek doğru değildir:
{
"success": false,
"error": "Product not found"
}Bu yanıt 200 OK ile gönderilirse HTTP seviyesinde işlem başarılı görünür. API istemcileri, izleme sistemleri ve ara katmanlar hatayı doğru sınıflandıramaz.
Durum kodu, isteğin sonucunu mümkün olduğunca doğru ifade etmelidir.
Başarılı isteklerde hangi durum kodları kullanılmalı?
200 OK
İstek başarıyla tamamlandığında ve yanıtta içerik döndürüldüğünde kullanılır:
GET /products/42
HTTP/1.1 200 OKPUT veya PATCH sonrasında güncellenen kaynak döndürülüyorsa da 200 OK kullanılabilir.
201 Created
Yeni bir kaynak başarıyla oluşturulduğunda kullanılır:
POST /products
HTTP/1.1 201 CreatedMümkünse Location başlığıyla oluşturulan kaynağın adresi de gönderilmelidir.
202 Accepted
İstek kabul edilmiş ancak işlem henüz tamamlanmamışsa kullanılır:
POST /reports
HTTP/1.1 202 AcceptedUzun süren raporlama veya dosya işleme görevlerinde istemciye işlem durumunu takip edebileceği bir kaynak verilebilir:
{
"jobId": "job-781",
"status": "queued",
"statusUrl": "/jobs/job-781"
}204 No Content
İşlem başarıyla tamamlandığında ancak yanıt gövdesi gönderilmeyecekse kullanılır:
DELETE /products/42
HTTP/1.1 204 No Content204 yanıtında gövde bulunmamalıdır.
İstemci hatalarında hangi durum kodları kullanılmalı?
400 Bad Request
İstek biçimsel olarak hatalıysa veya sunucu tarafından işlenemiyorsa kullanılır:
{
"type": "invalid-request",
"title": "Geçersiz istek",
"status": 400,
"detail": "İstek gövdesi geçerli bir JSON belgesi değil."
}401 Unauthorized
Kimlik doğrulaması yapılmamışsa veya gönderilen kimlik bilgileri geçersizse kullanılır.
Adındaki “Unauthorized” ifadesine rağmen bu kod çoğunlukla kimlik doğrulama eksikliğini bildirir:
GET /profile
HTTP/1.1 401 Unauthorized403 Forbidden
Kullanıcının kimliği biliniyor ancak ilgili işlemi yapma yetkisi bulunmuyorsa kullanılır:
DELETE /users/15
HTTP/1.1 403 ForbiddenKısaca:
401: Kim olduğunuzu doğrulayamadım.403: Kim olduğunuzu biliyorum ancak bu işleme izniniz yok.
404 Not Found
Talep edilen kaynak bulunamadığında kullanılır:
GET /products/9999
HTTP/1.1 404 Not FoundBazı sistemler, erişim izni bulunmayan hassas bir kaynağın varlığını gizlemek için de 404 döndürebilir.
405 Method Not Allowed
Endpoint mevcut ancak kullanılan HTTP metodu desteklenmiyorsa kullanılır:
DELETE /reports/monthly
HTTP/1.1 405 Method Not Allowed
Allow: GET409 Conflict
İstek mevcut kaynak durumuyla çakışıyorsa kullanılır.
Örnekler:
- Aynı benzersiz alanla ikinci kayıt oluşturulması
- Daha önce tamamlanan siparişin iptal edilmeye çalışılması
- Kaynak sürümleri arasında güncelleme çakışması
{
"type": "email-conflict",
"title": "Kayıt çakışması",
"status": 409,
"detail": "Bu e-posta adresi başka bir kullanıcı tarafından kullanılıyor."
}422 Unprocessable Content
İstek sözdizimi açısından geçerli olduğu hâlde alan doğrulamaları veya iş kuralları nedeniyle işlenemiyorsa kullanılabilir:
{
"type": "validation-error",
"title": "Doğrulama hatası",
"status": 422,
"errors": {
"email": ["Geçerli bir e-posta adresi girilmelidir."],
"birthDate": ["Doğum tarihi gelecekte olamaz."]
}
}Bazı API’ler doğrulama hatalarında 400 Bad Request kullanır. Her iki yaklaşımda da API genelinde tutarlılık önemlidir.
429 Too Many Requests
İstemci belirlenen istek sınırını aştığında kullanılır:
HTTP/1.1 429 Too Many Requests
Retry-After: 60Mümkünse istemcinin ne kadar süre sonra tekrar deneyebileceği belirtilmelidir.
Sunucu hatalarında hangi durum kodları kullanılmalı?
500 Internal Server Error
Sunucuda beklenmeyen bir hata oluştuğunda kullanılır.
Yanıtta veri tabanı sorguları, dosya yolları veya stack trace gibi hassas teknik ayrıntılar paylaşılmamalıdır. Hatanın ayrıntıları sunucu kayıtlarına yazılmalı ve kullanıcıya güvenli bir hata kimliği verilebilir.
502 Bad Gateway
Gateway veya proxy, arka plandaki servisten geçerli bir yanıt alamadığında kullanılır.
Mikroservis mimarileri ve reverse proxy kullanılan sistemlerde görülebilir.
503 Service Unavailable
Hizmet geçici olarak kullanılamıyorsa kullanılır. Bakım, aşırı yük veya geçici bağımlılık sorunları buna neden olabilir.
HTTP/1.1 503 Service Unavailable
Retry-After: 120504 Gateway Timeout
Gateway veya proxy, arka plandaki servisten zamanında yanıt alamadığında kullanılır.
İstemcinin tekrar deneme politikası, işlemin idempotent olup olmadığı dikkate alınarak tasarlanmalıdır.
API hata yanıtları nasıl tasarlanmalıdır?
Durum kodu tek başına her zaman yeterli değildir. İstemcinin hatayı anlayabilmesi ve kullanıcıya doğru mesaj gösterebilmesi için tutarlı bir hata gövdesi sunulmalıdır.
Örnek hata yanıtı:
{
"type": "https://api.example.com/problems/validation-error",
"title": "Doğrulama hatası",
"status": 422,
"detail": "Gönderilen alanlardan bazıları geçersiz.",
"instance": "/orders",
"traceId": "01J8B7M4Q9A6",
"errors": {
"items": ["Siparişte en az bir ürün bulunmalıdır."]
}
}İyi bir hata yanıtı şu bilgileri içerebilir:
- Makine tarafından işlenebilir hata kodu
- İnsan tarafından okunabilir açıklama
- HTTP durum kodu
- Hatanın oluştuğu kaynak
- Alan bazlı doğrulama hataları
- Destek ve kayıt takibi için istek kimliği
RFC 9457 ile tanımlanan “Problem Details for HTTP APIs” formatı, standart hata yanıtları oluşturmak için kullanılabilir.
Liste endpoint’lerinde filtreleme ve sayfalama
Büyük koleksiyonların tamamını tek yanıtta döndürmek performans sorunlarına neden olur:
GET /productsListe endpoint’leri filtreleme, sıralama ve sayfalama özellikleri sunmalıdır:
GET /products?categoryId=8&status=active&sort=-createdAt&page=2&pageSize=20Örnek yanıt:
{
"items": [
{
"id": 42,
"name": "Mekanik Klavye"
}
],
"pagination": {
"page": 2,
"pageSize": 20,
"totalItems": 148,
"totalPages": 8
}
}Sık değişen veya çok büyük veri kümelerinde sayfa numarası yerine cursor tabanlı sayfalama daha tutarlı ve verimli olabilir:
GET /products?limit=20&after=eyJpZCI6NDJ9API sürümleme nasıl yapılmalıdır?
API değişiklikleri mevcut istemcileri bozabilecekse sürümleme stratejisi gerekir.
URL üzerinden sürümleme:
GET /v1/productsHeader üzerinden sürümleme:
Accept: application/vnd.example.v2+jsonURL sürümleme daha görünür ve kullanımı kolaydır. Header sürümleme ise URI’leri kaynak odaklı tutabilir ancak istemci ve hata ayıklama sürecini karmaşıklaştırabilir.
Hangi yöntem seçilirse seçilsin:
- Geriye dönük uyumlu değişiklikler mümkün olduğunca sürüm artırmadan yapılmalı,
- Eski sürümlerin destek süresi açıklanmalı,
- Kullanımdan kaldırma tarihleri önceden bildirilmeli,
- Geçiş dokümantasyonu sunulmalıdır.
REST API tasarımında sık yapılan hatalar
Endpoint’lere fiil eklemek
POST /createOrder
POST /cancelOrderBunun yerine kaynakları modellemek daha tutarlıdır:
POST /orders
POST /orders/824/cancellationsHer sonuca 200 döndürmek
Hataların 200 OK içerisinde gönderilmesi standart HTTP araçlarının doğru çalışmasını zorlaştırır. İsteğin sonucuna uygun durum kodu seçilmelidir.
Veri tabanı şemasını doğrudan dışarı açmak
İç tablo ve sütun yapılarının API’ye birebir yansıtılması, veri tabanı değişikliklerinin istemcileri bozmasına yol açabilir.
Tutarsız alan adları kullanmak
Aynı API içerisinde aşağıdaki alanların karıştırılması kullanım zorluğu yaratır:
{
"user_id": 15,
"productId": 42,
"OrderID": 824
}Tek bir adlandırma standardı seçilmelidir.
Gereksiz iç içe kaynaklar oluşturmak
Kaynak ilişkilerini URL’de göstermek yararlıdır ancak çok derin endpoint’ler bakım maliyetini artırır.
Hassas hata ayrıntılarını paylaşmak
Veri tabanı hataları, sunucu yolları ve stack trace bilgileri istemciye gönderilmemelidir.
Dokümantasyonu sonradan hazırlamak
API sözleşmesi geliştirme sürecinin başında belirlenmelidir. OpenAPI gibi araçlarla dokümantasyon ve sözleşme birlikte yönetilebilir.
Örnek bir sipariş API’si
Tutarlı bir sipariş API’si şu endpoint’leri sunabilir:
GET /orders
POST /orders
GET /orders/{orderId}
PATCH /orders/{orderId}
GET /orders/{orderId}/items
POST /orders/{orderId}/cancellations
GET /orders/{orderId}/payments
POST /orders/{orderId}/paymentsÖrnek akış:
POST /ordersBaşarılı yanıt:
HTTP/1.1 201 Created
Location: /orders/824{
"id": 824,
"status": "pending",
"totalAmount": 1450,
"currency": "TRY"
}Siparişte stokta olmayan bir ürün varsa:
HTTP/1.1 409 Conflict{
"type": "stock-conflict",
"title": "Stok çakışması",
"status": 409,
"detail": "Ürünlerden biri talep edilen miktarda stokta bulunmuyor."
}Gönderilen alanlar geçersizse:
HTTP/1.1 422 Unprocessable ContentBu yapı, istemcinin her sonucu ayrı ve öngörülebilir biçimde yönetmesini sağlar.
REST API tasarım kontrol listesi
Yeni bir endpoint’i yayına almadan önce şu sorular gözden geçirilebilir:
- Kaynak iş alanında doğru şekilde modellenmiş mi?
- Endpoint adı isimlerden oluşuyor mu?
- Tekil ve çoğul kullanım tutarlı mı?
- Doğru HTTP metodu kullanılıyor mu?
- Başarılı sonuç için doğru durum kodu seçilmiş mi?
- Doğrulama ve iş kuralı hataları ayrıştırılmış mı?
- Hata yanıtı diğer endpoint’lerle aynı formatta mı?
- Yetkilendirme kontrolleri uygulanmış mı?
- Tekrarlanan isteklerin etkisi düşünülmüş mü?
- Liste sonuçlarında sayfalama var mı?
- Hassas bilgiler yanıttan çıkarılmış mı?
- Endpoint OpenAPI dokümanına eklenmiş mi?
- Geriye dönük uyumluluk değerlendirilmiş mi?
- Performans ve istek sınırları belirlenmiş mi?
Sonuç
İyi bir REST API tasarımı; anlaşılır kaynaklar, tutarlı endpoint’ler ve doğru HTTP durum kodları üzerine kurulur.
Kaynak isimlerinde fiiller yerine isimlerin kullanılması, HTTP metotlarının anlamlarına uygun seçilmesi ve hataların standart yanıtlarla sunulması API’nin öğrenilmesini kolaylaştırır. Doğru tasarım aynı zamanda istemci kodunu sadeleştirir, izlenebilirliği artırır ve sistem büyüdükçe ortaya çıkabilecek entegrasyon sorunlarını azaltır.
En önemli konu, her projede kusursuz bir REST saflığı yakalamak değil; iş ihtiyaçlarını karşılayan, öngörülebilir ve dokümante edilmiş bir API sözleşmesi oluşturmaktır.
Sıkça Sorulan Sorular
REST API endpoint nedir?
Endpoint, istemcinin belirli bir API kaynağına erişmek veya kaynak üzerinde işlem yapmak için kullandığı adrestir. Örneğin /products/42, kimliği 42 olan ürünün endpoint’i olabilir.
Endpoint isimlerinde fiil kullanılmalı mı?
Genellikle hayır. Endpoint kaynakları isimlerle, yapılacak işlem ise HTTP metoduyla ifade edilmelidir. /getProducts yerine GET /products kullanılması daha tutarlıdır.
POST ile PUT arasındaki fark nedir?
POST çoğunlukla koleksiyon altında yeni kaynak veya işlem oluşturmak için kullanılır. PUT ise belirli bir URI’deki kaynağın tam durumunu değiştirmek veya o adreste kaynak oluşturmak için kullanılabilir. PUT’un idempotent olması beklenir.
PUT ile PATCH arasındaki fark nedir?
PUT genellikle kaynağın tamamını değiştirmek, PATCH ise yalnızca belirli alanlarını güncellemek için kullanılır. Kesin davranış API sözleşmesinde açıkça belirtilmelidir.
401 ve 403 durum kodlarının farkı nedir?
401 Unauthorized, geçerli kimlik doğrulamasının bulunmadığını belirtir. 403 Forbidden, kullanıcının kimliği doğrulanmış olsa bile ilgili işlemi yapma yetkisinin olmadığını ifade eder.
Doğrulama hatalarında 400 mü, 422 mi kullanılmalı?
Her iki yaklaşım da kullanılabilir. 400, isteğin genel olarak hatalı olduğunu; 422 ise biçimsel olarak geçerli isteğin alan veya iş kuralları nedeniyle işlenemediğini daha ayrıntılı ifade eder. API genelinde tutarlı davranılması önemlidir.
Başarılı DELETE isteği hangi durum kodunu döndürmelidir?
Yanıt gövdesi bulunmuyorsa 204 No Content kullanılabilir. Silinen kaynak veya işlem sonucu döndürülecekse 200 OK tercih edilebilir.
REST API’lerde her zaman JSON mu kullanılır?
Hayır. REST belirli bir veri formatını zorunlu tutmaz. JSON yaygın olsa da XML, metin, görsel veya farklı medya türleri kullanılabilir. İçerik türü HTTP başlıklarıyla belirtilir.