İçeriğe geç
Muhammet Şafak
Web Geliştirme 3 dk okuma

REST API'de sayfalama, filtreleme ve sıralama

Büyüyen veri kümelerini istemciye yönetilebilir sunmak için API'de sayfalama, filtreleme ve sıralama parametrelerini nasıl tasarladığımı anlatıyorum.


Bir API ucu yazdığınızda ve ilk denemede tüm kayıtları tek seferde döndürdüğünüzde, küçük ölçekte bu sorun yaratmaz. Ama tablo büyüdüğünde ve istemci 10.000 kayıt almaya başladığında hem sunucu hem de istemci yavaşlar. Bunun yanı sıra istemci genellikle tümünü değil, belirli bir kısmını — belirli koşullara uyan, belirli bir sıraya göre düzenlenmiş kayıtları — istiyor.

Sayfalama, filtreleme ve sıralama bu sorunu çözen üç ayrı ama birlikte düşünülmesi gereken kavramdır. Bu yazıda bunları API düzeyinde nasıl kurduğumu anlatıyorum.

Sayfalama

En yaygın sayfalama yöntemi, page ve per_page parametrelerini kullanmak:

GET /api/urunler?page=2&per_page=20

Laravel’de paginate() metodu bunu zaten destekliyor; sorguya yalnızca per_page değerini geçirmek yeterli:

<?php

namespace App\Http\Controllers\Api;

use App\Urun;
use Illuminate\Http\Request;
use App\Http\Controllers\Controller;

class UrunController extends Controller
{
    public function index(Request $request)
    {
        $adet = $request->input('per_page', 20);

        // Maksimum sınır koyuyorum — istemcinin 1000 kayıt istemesini engelliyorum
        $adet = min($adet, 100);

        $urunler = Urun::paginate($adet);

        return response()->json($urunler);
    }
}

paginate() yanıta total, per_page, current_page, last_page gibi meta bilgileri de ekliyor; istemci kaç sayfa olduğunu ve şu an hangi sayfada olduğunu bu bilgilerden anlıyor.

Filtreleme

Filtreleme için URL parametrelerini kullanıyorum:

GET /api/urunler?kategori=elektronik&min_fiyat=100&max_fiyat=500

Bu parametreleri sorguya yansıtmak için koşullu bloklar yazıyorum:

public function index(Request $request)
{
    $sorgu = Urun::query();

    if ($request->filled('kategori')) {
        $sorgu->where('kategori', $request->input('kategori'));
    }

    if ($request->filled('min_fiyat')) {
        $sorgu->where('fiyat', '>=', (float) $request->input('min_fiyat'));
    }

    if ($request->filled('max_fiyat')) {
        $sorgu->where('fiyat', '<=', (float) $request->input('max_fiyat'));
    }

    if ($request->filled('arama')) {
        $aranan = '%' . $request->input('arama') . '%';
        $sorgu->where('ad', 'like', $aranan);
    }

    $adet    = min($request->input('per_page', 20), 100);
    $urunler = $sorgu->paginate($adet);

    return response()->json($urunler);
}

filled() metodu hem parametre varlığını hem de boş olmadığını kontrol ediyor; boş string gönderildiğinde filtre uygulanmıyor.

Sıralama

Sıralama için sort ve order parametreleri kullanıyorum:

GET /api/urunler?sort=fiyat&order=asc
GET /api/urunler?sort=olusturulma_tarihi&order=desc

Güvenli bir yaklaşım için izin verilen alanları beyaz listeyle kontrol ediyorum; istemcinin herhangi bir alana göre sıralama yapmasına izin vermiyorum:

public function index(Request $request)
{
    $sorgu = Urun::query();

    // Filtreleme (yukarıdaki gibi)...

    // İzin verilen sıralama alanları
    $izinliAlanlar = ['ad', 'fiyat', 'created_at'];
    $siralama      = $request->input('sort', 'created_at');
    $yon           = $request->input('order', 'desc');

    if (in_array($siralama, $izinliAlanlar)) {
        $sorgu->orderBy($siralama, $yon === 'asc' ? 'asc' : 'desc');
    }

    $adet    = min($request->input('per_page', 20), 100);
    $urunler = $sorgu->paginate($adet);

    return response()->json($urunler);
}

in_array kontrolü olmadan istemci sort=password gibi hassas alanlara sıralama yapabilir ya da ham SQL enjeksiyonu denemesi olabilir. Beyaz liste bu riski ortadan kaldırıyor.

Yanıt yapısı

Sayfalama meta bilgisini gövdede ayrı tutmak, istemcinin yanıtı kolayca işlemesini sağlıyor. Laravel’in paginate() çıktısı şuna benziyor:

{
    "current_page": 2,
    "data": [
        { "id": 21, "ad": "Klavye", "fiyat": 249.90 },
        { "id": 22, "ad": "Mouse", "fiyat": 189.50 }
    ],
    "from": 21,
    "last_page": 15,
    "per_page": 20,
    "to": 40,
    "total": 287
}

İstemci total ve per_page değerlerinden kaç sayfa olduğunu hesaplayabiliyor, current_page ile nerede olduğunu biliyor.

Birleşik örnek

Gerçek bir çağrı şöyle görünüyor:

GET /api/urunler?kategori=elektronik&min_fiyat=100&sort=fiyat&order=asc&page=1&per_page=15

Bu çağrı: elektronik kategorisinden, 100 TL üzeri ürünleri, fiyata göre artan sırayla, ilk sayfada 15 adet döndürüyor.

Küçük ama önemli bir ayrıntı: parametre adı tutarlılığı

Bu üç özelliği tasarlarken en çok dikkat ettiğim şey, parametre adlarının başından tutarlı olmasıydı. Bir uca page, diğerine pageNumber, başka birine sayfa demek istemci geliştiricisini gereksiz yere yoruyor. API yüzeyinin tamamında aynı adları kullanmak, belgelemeyi azaltıyor ve hataları önlüyor.

Şu an için küçük bir proje de olsa, bu alışkanlığı başından kurmak sonraki uçları eklerken otomatik bir tutarlılık sağlıyor.

Kursor tabanlı sayfalama hakkında

page ve per_page ile sayfalama, büyük tablolarda bir sorun yaratıyor: OFFSET sorgusu. On bininci sayfaya gitmek için veritabanının 200.000 kaydı okuyup geçmesi gerekiyor; bu tablo büyüdükçe yavaşlıyor.

Şu an bu sorunu yaşamıyorum çünkü tablolarım henüz o ölçeğe ulaşmadı. Ama cursor pagination denen bir alternatif var: “son gördüğüm kaydın ID’sini ver, ondan sonrasını getir” mantığıyla çalışıyor. Laravel’de cursorPaginate() metodu bu yöntemi uyguluyor. Ölçek sorunu olmadığında paginate() yeterli; gerektiğinde geçiş yapmak nispeten kolay.


Bu üç özelliği başından doğru kurmak, sonraki geliştirmeleri kolaylaştırıyor. İstemci geliştiricisi sayfalamayı kendi başına çözmek zorunda kalmıyor; filtreleme için ayrı uç noktalar açmak gerekmediğinden API yüzeyi küçük kalıyor. Mobil istemci veya üçüncü parti bir entegrasyon geldiğinde, aynı parametrelerle aynı uç noktayı kullanıyor. Bu tutarlılık başta görünmez ama ilerledikçe değerini hissettiriyor; yeni bir istemci için ayrı bir uç yazmak ya da farklı sayfalama mantığı uydurmak gerekmediğinde kazanılan zaman birikiyor.

Etiketler: #API
Paylaş:

İlgili Yazılar

Sitede Ara

Yazı, proje ve sayfalarda arama yapmak için yazmaya başlayın.

Esc ile kapat Pagefind ile güçlendirildi