
Foto oleh Santhosh Kanthala via Pexels
Pagination, filtering, dan sorting adalah tiga kemampuan penting yang harus dimiliki endpoint API yang mengembalikan koleksi data, terutama ketika jumlah data bisa bertumbuh besar seiring waktu dan mengembalikan seluruh data sekaligus akan sangat tidak efisien baik dari sisi performa maupun penggunaan bandwidth.
Pagination membatasi jumlah data yang dikembalikan per request, filtering memungkinkan client mengambil hanya subset data yang relevan sesuai kriteria tertentu, dan sorting memungkinkan client menentukan urutan data yang diterima sesuai kebutuhan tampilan mereka.
Artikel ini membahas implementasi ketiga kemampuan ini di ASP.NET Core Web API, termasuk offset-based pagination yang paling umum dipakai serta pertimbangan performa untuk dataset yang sangat besar.
Offset-Based Pagination dengan Page dan PageSize
Skip dan Take mengimplementasikan pagination berbasis halaman
Filtering dengan Query Parameter
Filter dibangun secara dinamis berdasarkan parameter yang diberikan
Sorting dengan Parameter Dinamis
Switch expression memilih kolom pengurutan secara dinamis
Menyertakan Metadata Pagination di Response
Metadata pagination membantu client membangun navigasi halaman
Cursor-based Pagination sebagai Alternatif Offset Pagination
Offset pagination vs cursor-based pagination pada dataset yang terus bertambah
Offset pagination dengan parameter page dan limit mudah diimplementasikan, namun memiliki kelemahan ketika data terus berubah di antara dua permintaan halaman. Jika ada baris baru disisipkan sebelum offset yang sedang diambil, baris yang seharusnya muncul di halaman berikutnya bisa ikut bergeser, menyebabkan client melewatkan atau menerima data duplikat tanpa disadari.
Cursor-based pagination mengatasi masalah ini dengan menjadikan nilai unik dari baris terakhir yang sudah diambil, biasanya Id atau kombinasi timestamp dan Id, sebagai acuan untuk mengambil batch berikutnya. Karena query memfilter berdasarkan nilai cursor dan bukan posisi baris, penyisipan data baru di awal tabel tidak akan mempengaruhi urutan atau kelengkapan data yang sudah diambil pada halaman sebelumnya.
Trade-off dari cursor-based pagination adalah client tidak bisa langsung melompat ke halaman tertentu seperti 'halaman 10', karena akses data bersifat sekuensial mengikuti cursor. Pendekatan ini paling cocok untuk feed data yang terus bertambah seperti notifikasi atau log transaksi, sementara offset pagination masih relevan untuk tabel data yang relatif statis dan membutuhkan navigasi halaman bebas.
Filtering dengan Operator Query String dan Sorting Multi-Kolom
Query string dengan operator gte/lte dan sorting multi-kolom memakai tanda minus
Filtering sederhana dengan kesetaraan seperti ?kategori=elektronik cukup untuk kasus dasar, namun kebutuhan nyata sering memerlukan perbandingan rentang nilai seperti harga minimum dan maksimum sekaligus. Konvensi umum menuliskan operator dalam kurung siku setelah nama field, misalnya harga[gte] untuk greater than or equal dan harga[lte] untuk less than or equal, yang kemudian diterjemahkan oleh server menjadi klausa WHERE dengan operator perbandingan yang sesuai. Operator seperti ne (not equal) dan in (daftar nilai) juga sering ditambahkan untuk mendukung filter yang lebih kompleks dari sisi client.
Sorting multi-kolom memungkinkan client menentukan lebih dari satu kriteria pengurutan sekaligus, dengan konvensi memisahkan nama kolom memakai koma dan menambahkan tanda minus di depan nama kolom untuk urutan menurun. Permintaan sort=kategori,-harga berarti data diurutkan berdasarkan kategori secara ascending terlebih dahulu, dan untuk kategori yang sama diurutkan lagi berdasarkan harga secara descending, mirip dengan klausa ORDER BY bertingkat pada SQL. Dokumentasi API perlu menjelaskan urutan prioritas sort secara eksplisit, agar konsumen API memahami kolom mana yang menjadi kriteria utama pengurutan.
Implementasi filtering dinamis seperti ini perlu divalidasi ketat di sisi server agar nama field dan operator yang diterima dibatasi pada daftar yang sudah ditentukan, bukan diteruskan mentah ke query builder. Tanpa validasi, mekanisme ini berisiko menjadi celah SQL injection atau memungkinkan client melakukan query terhadap kolom yang seharusnya tidak boleh diakses dari luar, seperti kolom harga modal yang bersifat internal. Pembatasan jumlah kolom yang boleh dipakai untuk filtering dan sorting juga membantu menjaga performa query tetap terkendali pada tabel berukuran besar.
Partial Response dengan Parameter Fields untuk Payload Lebih Ringkas
Query parameter fields memangkas kolom yang tidak diperlukan client
Endpoint yang selalu mengembalikan seluruh kolom resource membebani bandwidth ketika client sebenarnya hanya butuh sebagian kecil data, misalnya tampilan daftar produk di mobile yang hanya perlu nama dan harga saja. Parameter query seperti fields memungkinkan client menentukan kolom mana saja yang ingin dikembalikan, sehingga server cukup memproyeksikan field yang diminta pada query database.
Implementasi di sisi server perlu membatasi nama kolom yang boleh diminta pada daftar yang memang ada di resource, agar permintaan fields yang keliru tidak menimbulkan error atau membocorkan kolom internal yang seharusnya tidak pernah diekspos. Pendekatan ini sangat membantu pada resource dengan banyak kolom opsional seperti deskripsi panjang atau data relasi, yang tidak selalu relevan untuk setiap tampilan client.
Kesalahan Umum yang Sering Terjadi
Mengembalikan Seluruh Data Tanpa Pagination Sama Sekali
Endpoint yang mengembalikan seluruh data tanpa batasan pagination akan menjadi sangat lambat dan boros bandwidth seiring data bertambah, serta berisiko membebani server jika jumlah data sudah mencapai ratusan ribu atau jutaan baris.
Melakukan Filtering di Memori Setelah Data Diambil
Mengambil seluruh data terlebih dahulu dengan ToList() baru kemudian memfilternya dengan LINQ di memori aplikasi, alih-alih menerapkan Where sebelum ToList, membuat EF Core mengambil data jauh lebih banyak dari yang sebenarnya dibutuhkan dari database.
Tidak Membatasi Nilai Maksimum PageSize
Tidak membatasi nilai maksimum ukuranHalaman yang bisa diminta client memungkinkan seseorang meminta ukuranHalaman=1000000, yang secara efektif menghilangkan manfaat pagination dan tetap bisa membebani server.
Kapan Pendekatan Ini Tidak Cocok Dipakai
Offset-based pagination dengan Skip dan Take sederhana diimplementasikan dan dipahami, namun performanya menurun pada dataset sangat besar karena database tetap harus menghitung dan melompati baris-baris sebelumnya setiap kali.
Cursor-based pagination (menggunakan nilai unik terakhir sebagai acuan, bukan offset angka) memberikan performa lebih konsisten pada dataset besar, namun lebih kompleks diimplementasikan dan tidak mendukung lompat langsung ke halaman tertentu seperti yang bisa dilakukan offset-based pagination.
Ringkasan
Pagination, filtering, dan sorting adalah kemampuan esensial untuk endpoint API yang mengembalikan koleksi data, memastikan performa tetap baik dan client mendapatkan data yang relevan sesuai kebutuhan mereka. Selalu terapkan pagination sejak awal pada endpoint collection, dan batasi nilai parameter untuk mencegah penyalahgunaan yang bisa membebani server.
Seri Belajar REST API & Clean Architecture:
← Sebelumnya: Versioning API dan Kenapa Itu Penting
→ Selanjutnya: Pengenalan Clean Architecture pada Aplikasi Backend (segera terbit)