Koleksi tugas yang berisi sepuluh data memang enak dilihat sekaligus; koleksi yang berisi sepuluh ribu tidak. Endpoint yang mengembalikan seluruh isi tanpa batas membuat response membengkak, klien lambat, dan database bekerja lebih keras dari yang diperlukan — sementara klien sebenarnya hanya butuh satu halaman sekilas. Tiga teknik standar menyelesaikan masalah ini lewat query string: pagination membatasi jumlah data per permintaan, filtering menyaring sesuai kondisi, dan sorting mengatur urutan. Ketiganya kita terapkan sekaligus pada endpoint GET /tugas.
Parameter Query untuk Daftar #
Desain parameter mengikuti kebiasaan API modern:
| Parameter | Default | Makna | Contoh pemanggilan |
|---|---|---|---|
page | 1 | nomor halaman yang diminta | ?page=2 |
limit | 5 | jumlah maksimum data per halaman | ?limit=20 |
selesai | tidak ada | filter status tugas | ?selesai=false |
sort | id | field pengurutan; awalan - berarti menurun | ?sort=-judul |
Konvensi sort=-judul berasal dari dunia API publik: tanda minus di depan nama field berarti urutan menurun (Z ke A, besar ke kecil), tanpa tanda berarti menaik. Satu parameter, dua arah — lebih ringkas daripada sort=judul&order=desc.
Endpoint Paginasi Lengkap #
import json
from fastapi import FastAPI
from fastapi.testclient import TestClient
app = FastAPI()
tugas_db = [
{"id": 1, "judul": "Belajar Python", "selesai": True},
{"id": 2, "judul": "Belajar FastAPI", "selesai": False},
{"id": 3, "judul": "Belajar REST", "selesai": True},
{"id": 4, "judul": "Belajar Pydantic", "selesai": False},
{"id": 5, "judul": "Belajar OpenAPI", "selesai": True},
{"id": 6, "judul": "Belajar JWT", "selesai": False},
{"id": 7, "judul": "Belajar Testing", "selesai": True},
{"id": 8, "judul": "Belajar Versioning", "selesai": False},
]
@app.get("/tugas")
def daftar_tugas(
page: int = 1,
limit: int = 5,
selesai: bool | None = None,
sort: str = "id",
):
hasil = tugas_db
if selesai is not None:
hasil = [t for t in hasil if t["selesai"] == selesai]
desc = sort.startswith("-")
kolom = sort.lstrip("-")
hasil = sorted(hasil, key=lambda t: t[kolom], reverse=desc)
total = len(hasil)
mulai = (page - 1) * limit
data = hasil[mulai : mulai + limit]
return {
"data": data,
"meta": {
"total": total,
"page": page,
"limit": limit,
"has_next": mulai + limit < total,
},
}
client = TestClient(app)
r = client.get("/tugas")
print(json.dumps(r.json(), ensure_ascii=False, indent=2))
r = client.get("/tugas?page=2")
print([t["judul"] for t in r.json()["data"]], r.json()["meta"])
r = client.get("/tugas?selesai=false&sort=-judul&limit=2")
print([t["judul"] for t in r.json()["data"]], r.json()["meta"])
r = client.get("/tugas?sort=judul&limit=3&page=1")
print([t["judul"] for t in r.json()["data"]], r.json()["meta"])Output yang Dihasilkan:
{
"data": [
{
"id": 1,
"judul": "Belajar Python",
"selesai": true
},
{
"id": 2,
"judul": "Belajar FastAPI",
"selesai": false
},
{
"id": 3,
"judul": "Belajar REST",
"selesai": true
},
{
"id": 4,
"judul": "Belajar Pydantic",
"selesai": false
},
{
"id": 5,
"judul": "Belajar OpenAPI",
"selesai": true
}
],
"meta": {
"total": 8,
"page": 1,
"limit": 5,
"has_next": true
}
}
['Belajar JWT', 'Belajar Testing', 'Belajar Versioning'] {'total': 8, 'page': 2, 'limit': 5, 'has_next': False}
['Belajar Versioning', 'Belajar Pydantic'] {'total': 4, 'page': 1, 'limit': 2, 'has_next': True}
['Belajar FastAPI', 'Belajar JWT', 'Belajar OpenAPI'] {'total': 8, 'page': 1, 'limit': 3, 'has_next': True}Mari kita telaah alurnya:
- Baris 22-25 (
page: int = 1, ...): keempat parameter pagination adalah query parameter biasa — nilai default membuat klien cukup memanggilGET /tugastanpa query string sama sekali. - Baris 28-29 (
if selesai is not None:): filter memakaibool | NonesehinggaNoneberarti “tanpa filter”;?selesai=falsemenghasilkan list comprehension yang hanya menyisakan tugas yang belum selesai. - Baris 31-33 (
desc = sort.startswith("-")): tanda minus dibaca lalu dibuang darikolom—"-judul"menjadidesc=True, kolom="judul"— sebelumsorted()mengurutkan dengankey=lambda t: t[kolom]. - Baris 35-37 (
mulai = (page - 1) * limit): halaman dikonversi menjadi slice;page=2, limit=5memotong indeks 5 sampai 9. Inilah offset pagination — alternatifnyaskip/limitlangsung seperti pada chapter Dependency Injection. - Baris 38-46 (
return {...}): response dibungkus menjadidataplusmeta— jangan pernah mengembalikan list telanjang bila endpoint terpaginasi, karena klien kehilangan tempat menaruh metadata.
Metadata dalam Response #
Tiga baris output terakhir memperlihatkan kombinasi yang sesungguhnya: ?selesai=false&sort=-judul&limit=2 menyaring empat tugas belum selesai, mengurutkan menurun berdasarkan judul, lalu memotong dua data pertama — dan meta tetap jujur (total: 4, has_next: True) karena keduanya dihitung sebelum pemotongan. Berkat total dan has_next, klien tahu kapan berhenti meminta halaman berikutnya tanpa harus menebak.
Endpoint daftar kini layak tampil; tinggal satu pintu yang masih terbuka lebar — siapa pun bisa memanggil dan mengubah data. Chapter Authentication dan Authorization menutupnya.