Tüm Yazılar
KategoriBackend
Okuma Süresi
14 dk
Yayın Tarihi
2024-11-22
Kelime Sayısı
3.018kelime

Kahveni hazırla - bu içerikli bir makale!

REST API Tasarım Prensipleri: Kaynak, Hata, Pagination

Özet

Kaynak modelleme, RFC 9457 hata gövdesi, cursor pagination ve rate limiting başlıklarıyla production-ready bir REST API nasıl tasarlanır — Swift ve Flutter örnekleriyle.

REST API Tasarım Prensipleri: Kaynak, Hata, Pagination

Neden REST API tasarımı hâlâ zor bir problem

Mobil bir uygulamanın backend'i ile SwiftUI istemcisi arasındaki her gecikme, her tutarsız hata gövdesi, kullanıcı deneyimine doğrudan yansır. İyi tasarlanmış bir REST API; öngörülebilir kaynak modeli, tutarlı hata formatı ve doğru pagination stratejisiyle hem backend ekibinin hem mobil ekibin işini kolaylaştırır. Bu rehberde kaynak modellemeden idempotency'ye, RFC 9457 hata gövdesinden cursor pagination'a kadar production'da gerçekten işe yarayan prensipleri, Swift ve Flutter örnekleriyle birlikte ele alacağım.

💡 Pro Tip: API tasarımına önce endpoint listesiyle değil, kaynak (resource) ve ilişkileriyle başla — URL yapısı ve HTTP metot seçimi bu modelden kendiliğinden çıkar, tersini yaparsan sonradan versiyonlama krizine girersin.

İçindekiler

Kaynak Modelleme ve URL Tasarımı

REST'in temel birimi kaynaktır (resource), fiil değil. Bu yüzden URL'lerde fiil (verb) değil isim kullanılır: /orders koleksiyonu, /orders/{id} tekil kaynağı, /orders/{id}/items ise iç içe (nested) alt-koleksiyonu temsil eder.

Koleksiyon ve tekil kaynak ayrımı

  • Koleksiyon: GET /users — kullanıcı listesi döner, çoğul isimlendirme kullanılır.
  • Tekil kaynak: GET /users/42 — tek kullanıcıyı döner.
  • Alt-kaynak: GET /users/42/addresses — ilişkili koleksiyon; iki seviyeden fazla iç içelikten kaçın, üç seviyeyi geçen URL'ler genelde yanlış modelleme işaretidir.

Fiil yerine HTTP metodu

/orders/cancel-order gibi bir endpoint yerine POST /orders/{id}/cancellations veya PATCH /orders/{id} ile durum değişikliği (status: "cancelled") tercih edilmeli. Böylece kaynak URL'i sabit kalır, davranış HTTP metoduna taşınır — bu da istemci tarafında (Swift URLSession, Flutter dio) tek bir base path üzerinden CRUD mantığı kurmayı kolaylaştırır.

HTTP Metotları ve Idempotency

RFC 9110'a göre her HTTP metodunun güvenli (safe) ve idempotent olma özellikleri net tanımlıdır. GET, HEAD, OPTIONS güvenlidir — sunucu durumunu değiştirmez. PUT ve DELETE idempotenttir — aynı isteği N kere göndermek tek seferle aynı sonucu verir. POST ise ne güvenli ne idempotenttir; her çağrı yeni bir kaynak yaratabilir.

Idempotency-Key ile POST'u güvenli hale getirme

Mobil istemcilerde zayıf ağ bağlantısı yüzünden aynı ödeme isteğinin iki kez gönderilmesi klasik bir hatadır. Bunu önlemek için istemci, her POST isteğine benzersiz bir Idempotency-Key header'ı ekler; sunucu aynı anahtarla gelen ikinci isteği yeni bir işlem olarak değil, ilk isteğin sonucunu tekrar döndürerek işler. Bu desen Kasım 2024 itibarıyla Stripe gibi büyük API'lerin fiili (de facto) standardıydı ve IETF HTTPAPI çalışma grubunda taslak (Internet-Draft) aşamasındaydı — resmi RFC değildi, ama üretimde yaygın kabul görüyordu.

http
1POST /payments HTTP/1.1
2Host: api.example.com
3Content-Type: application/json
4Idempotency-Key: 7c9e6679-7425-40de-944b-e07fc1f90ae7
5 
6{"amount": 4999, "currency": "try", "orderId": "ord_123"}

PATCH ile kısmi güncelleme

Tam güncelleme için PUT (kaynağın tamamını değiştirir), kısmi güncelleme için PATCH kullanılır. PATCH gövdesinde iki yaygın format vardır: RFC 7396 JSON Merge Patch (basit alan güncellemesi) ve RFC 6902 JSON Patch (dizi/nesne üzerinde operasyon listesi — add, remove, replace). Çoğu mobil-öncelikli API için Merge Patch yeterlidir; JSON Patch'in operasyon listesi sözdizimi mobil tarafta gereksiz karmaşıklık yaratır.

Durum Kodları ve RFC 9457 Problem Details

Durum kodu seçimi, istemci tarafında hata yönetimini doğrudan belirler. 200 OK, 201 Created (Location header ile birlikte), 204 No Content (silme/güncelleme sonrası gövdesiz yanıt), 400 Bad Request (istemci hatası), 401 Unauthorized (kimlik doğrulama eksik), 403 Forbidden (yetki yok), 404 Not Found, 409 Conflict (eşzamanlılık çakışması), 422 Unprocessable Content (eski adıyla Unprocessable Entity; doğrulama hatası) ve 429 Too Many Requests en sık kullanılan kod setidir.

RFC 9457 ile standart hata gövdesi

RFC 9457 (Temmuz 2023'te RFC 7807'yi obsolete etti), hata yanıtları için makine-okunabilir bir JSON formatı tanımlar: type, title, status, detail, instance alanları zorunlu değil ama önerilir; API'ye özgü ek alanlar eklenebilir.

json
1{
2 "type": "https://api.example.com/errors/insufficient-funds",
3 "title": "Yetersiz Bakiye",
4 "status": 422,
5 "detail": "Hesap bakiyesi 49.99 TRY işlemi karşılamıyor.",
6 "instance": "/payments/abc123",
7 "balance": 12.5
8}

Bu format sayesinde mobil istemci, type alanına bakarak hangi hata için hangi kullanıcı arayüzünü göstereceğine programatik karar verebilir — string eşleştirme yerine yapısal kontrol yapılır. Content-Type: application/problem+json header'ı ile işaretlenir.

Concurrency çakışmalarında 409 ve ETag

İki kullanıcı aynı kaynağı eşzamanlı güncellerse 409 Conflict dönülmeli. Optimistic concurrency için RFC 9110'daki ETag / If-Match mekanizması kullanılır: istemci önce GET ile kaynağı ve ETag değerini alır, güncelleme isteğinde If-Match header'ına bu değeri koyar; sunucuda kaynak değişmişse 412 Precondition Failed döner.

Pagination: Offset vs Cursor

Büyüyen bir koleksiyonu sayfalamanın iki temel yaklaşımı var: offset-tabanlı ve cursor-tabanlı pagination. Mobil sonsuz-kaydırma (infinite scroll) listelerinde ikisi arasındaki fark performans ve tutarlılık açısından kritik.

Kriter
Offset (?page=3&limit=20)
Cursor (?after=eyJpZCI6...)
Performans (büyük tablo)
Yavaşlar (OFFSET tüm önceki satırları tarar)
Sabit, indeks üzerinden atlar
Yeni kayıt eklenince tutarlılık
Kayma olur (aynı öğe iki kez görünebilir)
Tutarlı, kayma yok
Rastgele sayfaya atlama
Kolay (page=7)
Zor/olanaksız
Örnek kullanan API
Klasik admin panelleri
Stripe, GitHub, Slack

Cursor pagination örneği (Stripe/GitHub deseni)

Stripe starting_after / ending_before, GitHub ise Link header ile rel="next" kullanır. İkisi de opak bir cursor değeri taşır — istemci bu değerin içeriğini çözmemeli, sadece bir sonraki isteğe aynen geçirmeli.

http
1GET /v1/orders?limit=20&starting_after=order_9f8e HTTP/1.1
2Host: api.example.com
3 
4HTTP/1.1 200 OK
5Link: <https://api.example.com/v1/orders?starting_after=order_a1b2&limit=20>; rel="next"

Mobil tarafta bu, hasNextPage + nextCursor alanlarını response gövdesine koyup istemcinin sadece bu iki alanı saklamasıyla basitleşir — sayfa numarası hesaplamak istemciye kalmaz.

Filtreleme, Sıralama ve Alan Seçimi

Query-string tasarımında tutarlılık önemli: filter[status]=active, sort=-createdAt (eksi işareti azalan sıra), fields=id,name,email (yalnız istenen alanları döndürme — mobil veri tüketimini azaltır) gibi bir konvansiyon seç ve tüm koleksiyon endpoint'lerinde aynı deseni uygula. JSON:API 1.1 spesifikasyonu bu üç deseni standardize eder ve birden fazla backend ekibi arasında ortak dil sağlar.

Alan seçiminin mobil etkisi

Bir sipariş listesi endpoint'i varsayılan olarak 40 alanlı bir nesne döndürüyorsa ama liste ekranı sadece 5 alan gösteriyorsa, fields parametresi ile gövde boyutu önemli ölçüde küçülür. Zayıf 4G/3G bağlantılarda bu, algılanan hızda doğrudan fark yaratır — özellikle sayfalanan listelerde her sayfa isteğinde tekrarlanan bir kazanımdır.

Versiyonlama Stratejileri

Üç yaygın versiyonlama yöntemi vardır: URL içinde (/v1/orders), header üzerinden (Accept: application/vnd.example.v2+json veya özel Api-Version header'ı) ve Stripe'ın kullandığı tarih-bazlı versiyonlama (Stripe-Version: 2024-06-20). URL versiyonlama en açık ve debug edilmesi en kolay yöntemdir — curl ile test ederken versiyon URL'de görünür durur; header-tabanlı versiyonlama ise URL'i temiz tutar ama araç desteği ve loglama tarafında ekstra dikkat ister. Mobil uygulamalarda App Store/Play Store inceleme süreleri nedeniyle istemci güncellemesi haftalar sürebilir — bu yüzden eski versiyonu en az 2-3 majör istemci sürümü boyunca canlı tutmak zorunludur.

Rate Limiting Başlıkları

Kasım 2024 itibarıyla rate limit başlıkları için tek bir resmi RFC yoktu; IETF HTTPAPI çalışma grubunun draft-ietf-httpapi-ratelimit-headers taslağı (o dönem draft-08) RateLimit ve RateLimit-Policy header'larını öneriyordu, ama pek çok API (GitHub, Twitter/X) kendi X-RateLimit-* başlıklarını kullanmaya devam ediyordu. Standart hâlâ oturmadığı için, API tasarlarken en azından şu üç bilgiyi bir şekilde döndür: limit, kalan istek sayısı, ve sıfırlanma zamanı.

http
1HTTP/1.1 429 Too Many Requests
2Retry-After: 30
3X-RateLimit-Limit: 100
4X-RateLimit-Remaining: 0
5X-RateLimit-Reset: 1732288800

Retry-After header'ı RFC 9110'un bir parçasıdır ve 429/503 yanıtlarında istemciye ne kadar bekleyip tekrar deneyeceğini saniye cinsinden söyler — mobil istemcide exponential backoff yerine doğrudan bu değeri kullanmak, gereksiz tekrar denemelerini önler.

Güvenlik Temelleri

REST API güvenliğinin üç temel ayağı var: kimlik doğrulama (authentication), yetkilendirme (authorization) ve girdi doğrulama (input validation). OWASP API Security Top 10 (2023 sürümü), en sık görülen zafiyetleri sıralar; başlıcaları Broken Object Level Authorization (BOLA — bir kullanıcının başka bir kullanıcının kaynağına ID değiştirerek erişmesi) ve Broken Authentication'dır.

Pratik kontrol listesi

  • Authentication: Bearer token (JWT) veya OAuth 2.0 — API key'i asla URL query-string'de taşıma, header'da taşı.
  • Authorization: Her endpoint'te kaynak sahipliğini kontrol et — GET /orders/42 isteğinde 42 numaralı siparişin gerçekten istek sahibine ait olduğunu doğrula (BOLA'ya karşı).
  • CORS: WHATWG Fetch standardına göre Access-Control-Allow-Origin başlığını mümkün olduğunca dar tut; * yalnızca tamamen public, kimlik doğrulamasız endpoint'lerde kabul edilebilir.
  • Input validation: Sunucu tarafında şema doğrulaması (Zod, JSON Schema) — istemci tarafı doğrulamasına asla güvenme.

Mobil İstemci Açısından Gerçek Örnekler

İyi tasarlanmış bir REST API, istemci tarafındaki kod miktarını da azaltır. Ağ katmanını doğru kurmak isteyenler için network layer optimizasyonu rehberine bakabilirsin; burada doğrudan API tasarımının istemci koduna nasıl yansıdığına odaklanıyoruz.

Swift: URLSession ile async/await ve cursor pagination

WWDC21'de gelen URLSession.shared.data(for:) async/await API'siyle, cursor-tabanlı bir liste isteği callback cehennemi olmadan yazılabilir:

swift
1enum APIError: Error {
2 case unexpectedStatus
3}
4 
5struct Order: Decodable {
6 let id: String
7 let amount: Int
8}
9 
10struct OrdersResponse: Decodable {
11 let items: [Order]
12 let nextCursor: String?
13}
14 
15func fetchOrders(after cursor: String? = nil, token: String) async throws -> OrdersResponse {
16 var components = URLComponents(string: "https://api.example.com/orders")!
17 if let cursor {
18 components.queryItems = [URLQueryItem(name: "after", value: cursor)]
19 }
20 var request = URLRequest(url: components.url!)
21 request.setValue("Bearer \(token)", forHTTPHeaderField: "Authorization")
22 let (data, response) = try await URLSession.shared.data(for: request)
23 guard let http = response as? HTTPURLResponse, http.statusCode == 200 else {
24 throw APIError.unexpectedStatus
25 }
26 return try JSONDecoder().decode(OrdersResponse.self, from: data)
27}

Swift concurrency'nin genel prensipleri için async/await best practices yazısını okumanı öneririm — özellikle Task iptali ile ağ isteklerinin senkronize edilmesi konusunda.

Flutter: dio ile Idempotency-Key ve interceptor

Flutter tarafında dio paketi, interceptor mekanizmasıyla her POST isteğine otomatik Idempotency-Key eklemeyi kolaylaştırır:

dart
1class IdempotencyInterceptor extends Interceptor {
2 @override
3 void onRequest(RequestOptions options, RequestInterceptorHandler handler) {
4 if (options.method == 'POST') {
5 options.headers['Idempotency-Key'] = const Uuid().v4();
6 }
7 handler.next(options);
8 }
9}
10 
11final dio = Dio()..interceptors.add(IdempotencyInterceptor());

Bu desen, kötü ağ koşullarında oluşan çift-tıklama/çift-istek senaryolarında sunucu tarafını koruyan tek satırlık bir güvenlik ağı sağlar.

ALTIN İPUCU

Bu yazının en değerli bilgisi

Bu ipucu, yazının en önemli çıkarımını içeriyor.

Easter Egg

Gizli bir bilgi buldun!

Bu bölümde gizli bir bilgi var. Keşfetmek ister misin?

Okuyucu Ödülü

Bu makalede anlatılan tüm prensipleri tek bir endpoint tasarımında uygulayıp uygulamadığını kontrol edebileceğin, kopyala-yapıştır kullanıma hazır bir REST API tasarım kontrol listesi ve minimal bir Problem Details şablonu hazırladım. Yeni bir endpoint yazmadan önce bu listeyi gözden geçirmek, code review'da geri dönüşleri önemli ölçüde azaltır.

SSS

REST API tasarımında offset yerine her zaman cursor pagination mı kullanmalıyım?

Hayır. Cursor pagination büyük ve sık değişen koleksiyonlarda (feed, sipariş listesi) üstündür, ama kullanıcının "7. sayfaya git" gibi rastgele sayfa numarasına atlamasını gerektiren admin panellerinde offset pagination hâlâ daha pratiktir. Koleksiyon büyüklüğü ve erişim deseni seçimi belirlemeli.

Idempotency-Key her POST isteğinde zorunlu mu?

Hayır, yalnızca tekrarlanması riskli (ödeme, sipariş oluşturma, e-posta gönderimi gibi) mutasyon endpoint'lerinde gereklidir. Salt-okunur veya tekrarlanması zararsız işlemlerde (örneğin bir log kaydı ekleme) ek karmaşıklık getirmesine gerek yok.

RFC 9457 kullanmak zorunda mıyım, yoksa kendi hata formatımı mı tasarlamalıyım?

Zorunlu değil ama şiddetle önerilir. Kendi formatını tasarlarsan, her yeni istemci (Swift, Flutter, web) için ayrı ayrı parse mantığı yazman gerekir; RFC 9457 gibi yaygın bir standarda uymak, üçüncü parti kütüphane ve tooling desteğinden (hata loglama, API gateway'ler) faydalanmanı sağlar.

API versiyon numarasını ne zaman artırmalıyım?

Yalnızca geriye dönük uyumsuz (breaking) bir değişiklik yaptığında: bir alanı kaldırmak, bir alanın tipini değiştirmek, zorunlu bir alan eklemek. Yeni bir opsiyonel alan eklemek veya yeni bir endpoint eklemek versiyon artırmayı gerektirmez — bu additive (eklemeli) değişikliklerdir.

Mobil uygulamalarda rate limiting'i nasıl kullanıcı dostu hale getiririm?

Retry-After header'ını sessizce arka planda kullan ve kullanıcıya ham "429 Too Many Requests" hatası gösterme; bunun yerine "Bir dakika sonra tekrar dene" gibi anlaşılır bir mesajla birlikte otomatik yeniden deneme (retry) mantığı kur, ama sınırsız retry döngüsüne girmemesi için maksimum deneme sayısı koy.

Güncelleme (Eylül 2026)

Bu makale ilk yayınlandığında (Kasım 2024) Idempotency-Key ve RateLimit header'ları henüz IETF taslağı aşamasındaydı — Eylül 2026 itibarıyla durum büyük ölçüde aynı: draft-ietf-httpapi-ratelimit-headers Mayıs 2026'da 11. taslağına ulaştı (geçerlilik sonu 24 Kasım 2026) ama hâlâ RFC olmadı, bir directorate review'ında "henüz hazır değil" notu aldı. Idempotency-Key taslağı da benzer şekilde RFC'ye dönüşmedi; son doğrulanabilir sürüm Ekim 2025 tarihli 7. taslak. Yani iki pratik desen de üç yıla yakındır fiili standart olmaya devam ediyor, resmiyet kazanmadı (kaynak: IETF Datatracker).

Buna karşılık iki somut gelişme var. Birincisi, uzun süredir tartışılan "body taşıyan safe method" fikri Haziran 2026'da RFC 10008 olarak HTTP QUERY metodunu resmileştirdi — GET gibi safe ve idempotent, ama POST gibi request body taşıyabilen, önbelleklenebilir bir metot (kaynak: RFC 10008). Karmaşık filtre/arama sorgularında URL uzunluk sınırını aşan API'ler için resmi bir çözüm sunuyor. İkincisi, OpenAPI 3.2.0 Eylül 2025'te yayınlandı ve hiyerarşik tag'ler, birinci-sınıf streaming desteği (SSE, JSON Lines) ve additionalOperations ile özel HTTP metodlarını (tam da QUERY gibi) tanımlama imkânı getirdi (kaynak: OpenAPI Specification v3.2.0, yayın: 19 Eylül 2025). OpenAPI 4.0 ("Project Moonwalk") ise Eylül 2026 itibarıyla hâlâ tasarım aşamasında, üretim tooling'i yok — 3.x hattı kısa-orta vadede birincil seçim olmaya devam ediyor (kaynak: OAI sig-moonwalk).

RFC 9457 Problem Details desteği aslında yeni değil: Spring Framework 6 / Spring Boot 3'ten (Kasım 2022) beri ProblemDetail sınıfıyla native destekleniyor ve bu destek Spring Boot 4'te de sürüyor, ASP.NET Core'da Results.Problem .NET 6'dan beri minimal API'lerde mevcut; .NET 7'de gelen AddProblemDetails() + IProblemDetailsService ile Problem Details framework-genelinde varsayılan hata şekli hâline geldi. Asıl boşluk Node ekosisteminde: Fastify ve Hono'da hâlâ çekirdek desteği yok, üçüncü parti paketlerle (@yeliex/fastify-problem-details, hono-problem-details) kapatılıyor (kaynak: Spring Framework — Error Responses). JSON:API tarafında v1.1 (2022) hâlâ tek stabil sürüm; v1.2 ise hâlâ "working draft" statüsünde (kaynak: jsonapi.org/format/1.2). Güvenlik cephesinde genel OWASP Top 10:2025 listesi Ocak 2026'da finalize oldu (Software Supply Chain Failures ve Mishandling of Exceptional Conditions yeni kategoriler olarak eklendi) — ancak bu API-özel liste değil, API Security Top 10'un ayrı bir 2026 revizyonu bu araştırmada doğrulanamadı (kaynak: OWASP Top 10:2025). Çalışma ortamı tarafında Node 24 LTS (Ekim 2025) Undici 7 ile daha katı fetch uyumluluğu getirdi; Swift 6.2'nin "approachable concurrency" modeli URLSession'ı actor-tabanlı kodda kullanmayı kolaylaştırdı.

Sonuç

Sağlam bir REST API tasarımı; net kaynak modeli, tutarlı hata gövdesi (RFC 9457), doğru pagination stratejisi ve düşünülmüş versiyonlama üzerine kurulur — bunların hiçbiri tek başına "en iyi pratik" değil, birlikte çalışan bir sistemdir. Mobil istemci tarafında bu prensipleri uygulamaya devam etmek istersen network layer optimizasyonu yazısı ağ katmanını production-ready hale getirmek için pratik adımlar sunuyor. Backend tarafını Swift ile kurmayı düşünüyorsan server-side Swift ve Swift Vapor ile backend API rehberlerine bakabilirsin. API güvenliğini derinleştirmek için iOS network security yazısı BOLA ve TLS pinning gibi konuları detaylandırıyor. Swift concurrency temellerini pekiştirmek istersen async/await best practices yazısı bu makaledeki URLSession örneklerinin arkasındaki mantığı açıklıyor.

Kaynaklar

Etiketler

#REST API#API Design#Backend#HTTP#Pagination#Swift#Flutter
Muhittin Çamdalı

Muhittin Çamdalı

Lead Mobile Engineer

12+ yıllık deneyime sahip Lead Mobile Engineer. Swift, SwiftUI, Kotlin ve Flutter ile iOS, Android ve cross-platform mimarilerde uzman. Performanslı ve kullanıcı dostu mobil uygulamalar geliştiriyorum.

iOS Geliştirme Haberleri

Haftalık Swift tips, SwiftUI tricks ve iOS best practices. Spam yok, sadece değerli içerik.

Gizliliğinize saygı duyuyoruz. İstediğiniz zaman abonelikten çıkabilirsiniz.

Paylaş

İlgili İçerik