REST API'de path parametresi (path parameter) hangi kaynağın kastedildiğini, query parametresi (query parameter) ise o kaynağın veya koleksiyonun hangi görünümünün istendiğini söyler. Kısa kural şudur: kimlik path'e, filtreleme, sıralama, sayfalama ve arama query'ye gider. /kullanicilar/42 tek bir kullanıcıyı adresler; /kullanicilar?rol=editor&page=2 ise aynı koleksiyonun süzülmüş ve sayfalanmış halini ister. Bir değeri değiştirdiğinizde başka bir kaynağa mı geçiyorsunuz, yoksa aynı listeye başka bir açıdan mı bakıyorsunuz sorusu çoğu durumda doğru yeri gösterir.

Bu yazıda iki parametre türünün HTTP tarafındaki yerini, karar verirken kullanabileceğiniz ölçütleri, bulunamayan kaynak ile boş sonuç arasındaki farkı ve FastAPI'nin iki türü nasıl ayırt ettiğini çalışan örneklerle ele alıyoruz. URL'deki kaynak adlarının nasıl seçileceği için endpoint isimlendirme kuralları, serinin tamamı için REST API tasarımı öğrenme rehberi sayfasına bakabilirsiniz.

URI'de path ve query nerede durur

RFC 9110, http ve https URI'lerinde hiyerarşik path bileşeni ile isteğe bağlı query bileşeninin birlikte, sunucunun ad alanı içindeki hedef kaynağı tanımladığını söyler. Query, soru işaretinden sonra gelen kısımdır. Bu tanımın tasarım açısından önemli bir sonucu vardır: HTTP açısından /kullanicilar?rol=admin ile /kullanicilar?rol=editor iki ayrı hedef URI'dir ve önbellekler (cache) onları ayrı ayrı saklayabilir. Yani query parametresi "aynı uç noktaya (endpoint) ek bilgi" değil, tanımlanan şeyin parçasıdır.

Fark teknik değil anlamsaldır. Path, kaynak hiyerarşisini ifade eder: koleksiyon, koleksiyondaki tek bir öğe, o öğeye bağlı alt koleksiyon. Query ise çoğunlukla bir koleksiyonun içeriğini daraltmak, sıralamak veya parçalara bölmek için kullanılır. Aşağıdaki iki istek bu ayrımı tipik haliyle gösterir:

http
GET /kullanicilar/42 HTTP/1.1
Host: api.ornek.com

GET /kullanicilar?rol=editor&durum=aktif&sort=-created_at&page=2&limit=20 HTTP/1.1
Host: api.ornek.com

İlk istekteki 42 değeri değişirse bambaşka bir kullanıcıya bakarsınız. İkinci istekte rol, durum, sort, page ve limit değişse bile hâlâ aynı /kullanicilar koleksiyonuna bakıyorsunuz; yalnızca hangi öğelerin, hangi sırayla ve hangi parçasının döneceği değişiyor.

Path parametresi ne zaman kullanılır

Path parametresi ne zaman kullanılır: Zorunludur, Sırası anlamlıdır, Tek bir değer taşır, Yoksa 404 doğurur

Path parametresi, kaynağı benzersiz biçimde tanımlayan değer içindir. Tipik örnekler kayıt kimliği (/kullanicilar/42), bir kaynağa bağlı alt koleksiyon (/kullanicilar/42/siparisler) ve adı sabit tekil alt kaynaklardır. Path parametrelerinin ortak özellikleri şunlardır:

  • Zorunludur: Path'teki bir segment boş bırakılamaz; /kullanicilar/ ile /kullanicilar/42 farklı uç noktalardır.
  • Sırası anlamlıdır: /kullanicilar/42/siparisler/7 içinde her segment bir öncekine bağlıdır.
  • Tek bir değer taşır: Bir path segmenti bir kaynağı gösterir, liste veya seçenek taşımaz.
  • Yoksa 404 doğurur: Gösterdiği kaynak bulunamazsa doğru yanıt 404 Not Found olur.

Hiyerarşi ne kadar derin olmalı sorusu ayrı bir tasarım konusudur. Kaynakları iç içe yazmak ilişkiyi açık gösterir ama URL'yi uzatır; yaygın tavsiye en fazla üç seviyede durmaktır. Bu kararın ayrıntısı için iç içe kaynaklar ve URL derinliği yazısına bakın.

Query parametresi ne zaman kullanılır

Query parametresi ne zaman kullanılır: Filtreleme, Sıralama, Sayfalama, Arama

Query parametreleri koleksiyonun görünümünü ayarlamak içindir ve dört ana işte kullanılır. Bu dört grubun her birinin ayrıntısı serinin başka yazılarında ele alınıyor; burada amaç doğru yeri seçmek:

AmaçÖrnekAyrıntı
FiltrelemeGET /users?role=admin&status=activeFiltreleme, sıralama ve arama yazısı
SıralamaGET /posts?sort=-created_atFiltreleme, sıralama ve arama yazısı
SayfalamaGET /users?page=2&limit=20Offset ve cursor sayfalama yazıları
AramaGET /users?q=johnFiltreleme, sıralama ve arama yazısı

Tablodaki örnekler İngilizce kaynak adlarıyla yazılmıştır; /users gibi İngilizce adlar da /kullanicilar gibi Türkçe adlar da kullanılır, önemli olan bir API içinde tek dil kullanmaktır. Query parametrelerinin ortak özellikleri path parametrelerinin neredeyse tersidir: genellikle isteğe bağlıdır ve varsayılan değerleri vardır, sıraları anlam taşımaz, birden fazla değer alabilir ve birbirleriyle birleştirilebilir. Ayrıntılar için filtreleme, sıralama ve arama, offset sayfalama ve cursor sayfalama yazılarına bakın.

Karar ölçütleri

Bir değerin nereye gideceğine karar verirken şu sorular işinizi kolaylaştırır:

SoruCevap evetse
Değer kaynağı benzersiz tanımlıyor mu?Path
Değer olmadan istek anlamsız mı kalıyor?Path
Değer değişince başka bir kaynağa mı geçiliyor?Path
Değer isteğe bağlı ve varsayılanı var mı?Query
Birden çok değer veya birleştirme gerekiyor mu?Query
Değer sonuç kümesini daraltıyor, sıralıyor veya bölüyor mu?Query
Değer gizli veya kişisel bir bilgi mi?İkisi de değil, istek gövdesi veya başlık

Son satır sık gözden kaçar. RFC 9110, URI'lerin paylaşılmak için tasarlandığını, güvenli kaynakları gösterseler bile ekranlarda, yer imlerinde ve sunucu ile vekil sunucu (proxy) kayıtlarında görünebildiğini hatırlatır; bu yüzden hassas, kişiyi tanımlayan veya ifşası riskli bilgiyi URI'ye koymayı akıllıca bulmaz. Parola, erişim anahtarı veya kimlik numarası gibi değerler ne path'te ne query'de olmalıdır. Kimlik bilgisi Authorization başlığına gider; ayrıntı için API key ile kimlik doğrulama yazısına bakın.

404 mü, boş liste mi

İki parametre türünü ayırmanın en pratik sonucu hata davranışında ortaya çıkar. Path parametresi bir kaynağı gösterdiği için o kaynak yoksa yanıt 404 Not Found olur. Query parametresi ise koleksiyonu süzer; hiç eşleşme olmaması bir hata değil, boş bir sonuçtur ve yanıt 200 OK ile boş bir data dizisidir. GET /kullanicilar?rol=yonetici isteği "böyle bir kullanıcı yok" demez, "bu filtreye uyan kullanıcı sayısı sıfır" der. İstemci açısından fark büyüktür: 404 alan bir istemci adresin yanlış olduğunu düşünür, boş liste alan ise sorgusunun sonuç vermediğini anlar.

Bu ayrım iç içe kaynaklarda da geçerlidir. GET /kullanicilar/42/siparisler isteğinde 42 numaralı kullanıcı yoksa 404 dönmek doğaldır, kullanıcı var ama siparişi yoksa 200 ve boş liste döner. Aynı bilgi GET /siparisler?kullanici_id=42 olarak istendiğinde ise kullanıcı olmasa bile boş liste dönmek tutarlı bir seçimdir, çünkü burada adreslenen kaynak sipariş koleksiyonudur ve kullanıcı yalnızca bir filtredir. Doğru seçimin ne olduğu, istemcinin hangi soruyu sorduğuna bağlıdır.

İç içe kaynak mı, filtre mi

http
GET /kullanicilar/42/siparisler HTTP/1.1
Host: api.ornek.com

GET /siparisler?kullanici_id=42&durum=kargoda HTTP/1.1
Host: api.ornek.com

İki istek benzer bir veri kümesi döndürebilir ama farklı şeyler söyler. İlki siparişleri kullanıcının alt kaynağı olarak modeller ve sahiplik ilişkisini URL'de görünür kılar; "bu kullanıcının siparişleri" sorusu için okunması kolaydır ve yetki denetimini de belirginleştirir. İkincisi siparişleri bağımsız bir koleksiyon olarak görür; birden fazla ölçütü birleştirmek (kullanici_id, durum, tarih aralığı) ya da kullanıcıdan bağımsız sorgular yapmak gerektiğinde daha esnektir. Hiyerarşik yapıyı tercih eden yaklaşım, /posts?user_id=123 biçimini ilişkiyi daha az açık gösterdiği için ikinci planda tutar. İki biçimi birlikte sunmak da mümkündür; önemli olan ikisinin aynı sonuçları aynı kurallarla döndürmesidir.

FastAPI'de path ve query parametreleri

FastAPI'de path ve query parametreleri: Sabit yol ile değişkenli yolun sırası, Birden fazla değer alan query parametresi

FastAPI iki türü fonksiyon imzasından ayırt eder: yol şablonunda {kullanici_id} olarak geçen ad path parametresi, şablonda olmayan basit tipli parametreler ise query parametresi olur. Varsayılan değeri olan query parametresi isteğe bağlıdır; Query(ge=1, le=100) ile sınır da koyabilirsiniz. Aşağıdaki örnekte veri bellek içi bir listede tutuluyor; üretimde bu bir veritabanı sorgusu olur.

python
from fastapi import FastAPI, HTTPException, Query
from fastapi.testclient import TestClient

app = FastAPI()
KULLANICILAR = [
    {"id": 1, "ad": "Ayşe", "rol": "admin", "durum": "aktif"},
    {"id": 2, "ad": "Mehmet", "rol": "editor", "durum": "pasif"},
    {"id": 3, "ad": "Zeynep", "rol": "editor", "durum": "aktif"},
]

@app.get("/kullanicilar/{kullanici_id}")           # path parametresi: hangi kaynak
def getir(kullanici_id: int):
    for k in KULLANICILAR:
        if k["id"] == kullanici_id:
            return k
    raise HTTPException(status_code=404, detail="Kullanıcı bulunamadı")

@app.get("/kullanicilar")                          # query parametreleri: koleksiyonun hangi görünümü
def listele(rol: str | None = None, durum: str | None = None,
            limit: int = Query(default=20, ge=1, le=100)):
    sonuc = [k for k in KULLANICILAR
             if (rol is None or k["rol"] == rol) and (durum is None or k["durum"] == durum)]
    return {"data": sonuc[:limit], "meta": {"total": len(sonuc), "limit": limit}}

c = TestClient(app)
r = c.get("/kullanicilar/3")
print(r.status_code, r.json())
# çıktı: 200 {'id': 3, 'ad': 'Zeynep', 'rol': 'editor', 'durum': 'aktif'}
r = c.get("/kullanicilar/9")
print(r.status_code, r.json())
# çıktı: 404 {'detail': 'Kullanıcı bulunamadı'}
r = c.get("/kullanicilar", params={"rol": "editor", "durum": "aktif"})
print(r.status_code, r.json())
# çıktı: 200 {'data': [{'id': 3, 'ad': 'Zeynep', 'rol': 'editor', 'durum': 'aktif'}], 'meta': {'total': 1, 'limit': 20}}
r = c.get("/kullanicilar?rol=yonetici")
print(r.status_code, r.json())
# çıktı: 200 {'data': [], 'meta': {'total': 0, 'limit': 20}}
r = c.get("/kullanicilar/abc")
hata = r.json()["detail"][0]
print(r.status_code, hata["loc"], hata["msg"])
# çıktı: 422 ['path', 'kullanici_id'] Input should be a valid integer, unable to parse string as an integer
r = c.get("/kullanicilar?limit=500")
hata = r.json()["detail"][0]
print(r.status_code, hata["loc"], hata["msg"])
# çıktı: 422 ['query', 'limit'] Input should be less than or equal to 100
r = c.get("/kullanicilar?rool=admin")
print(r.status_code, r.json()["meta"])
# çıktı: 200 {'total': 3, 'limit': 20}

Çıktılar yukarıdaki kuralları doğrular. Var olmayan 9 numaralı kullanıcı 404 alır, eşleşmeyen rol=yonetici filtresi ise 200 ve boş liste döndürür. Path parametresinin tipi int olduğu için /kullanicilar/abc isteği FastAPI tarafından 422 ile reddedilir ve hata konumu ["path", "kullanici_id"] olarak bildirilir. Üst sınırı aşan limit=500 da 422 alır; burada konum ["query", "limit"] olur.

Son satır önemli bir kenar durumunu gösteriyor: yanlış yazılmış rool=admin parametresi sessizce yok sayıldı ve istemci süzülmemiş listenin tamamını aldı. FastAPI tanımadığı query parametrelerini varsayılan olarak görmezden gelir ve bu, istemcide fark edilmesi zor hatalara yol açabilir. Kritik filtrelerde tanınmayan parametreyi 400 Bad Request ile reddetmek veya en azından belgelerde bu davranışı açıkça yazmak iyi bir alışkanlıktır.

Sabit yol ile değişkenli yolun sırası

/kullanicilar/ben gibi sabit bir alt yol ile /kullanicilar/{kullanici_id} aynı seviyede durduğunda yol eşleştirme sırası önem kazanır. FastAPI yolları tanımlandıkları sırayla dener; değişkenli yol önce tanımlanırsa ben metni kimlik olarak yorumlanmaya çalışılır.

python
from fastapi import FastAPI
from fastapi.testclient import TestClient

yanlis = FastAPI()

@yanlis.get("/kullanicilar/{kullanici_id}")
def getir(kullanici_id: int):
    return {"id": kullanici_id}

@yanlis.get("/kullanicilar/ben")
def ben():
    return {"id": 1, "kaynak": "oturum"}

dogru = FastAPI()
dogru.get("/kullanicilar/ben")(ben)               # sabit yol önce
dogru.get("/kullanicilar/{kullanici_id}")(getir)  # değişkenli yol sonra

print(TestClient(yanlis).get("/kullanicilar/ben").status_code)
# çıktı: 422
print(TestClient(dogru).get("/kullanicilar/ben").json())
# çıktı: {'id': 1, 'kaynak': 'oturum'}
print(TestClient(dogru).get("/kullanicilar/7").json())
# çıktı: {'id': 7}

İlk uygulamada /kullanicilar/ben isteği ben değerini tamsayıya çeviremediği için 422 aldı. Sabit yolu önce tanımlamak sorunu çözer. Bu durum, aynı segment konumunda hem kimlik hem de sabit ad kullanmanın tasarımda dikkat isteyen bir tercih olduğunu da gösterir; toplu işlem yazısındaki /kullanicilar/batch yolu için de aynı kural geçerlidir.

Birden fazla değer alan query parametresi

Bir filtreye birden fazla değer vermenin iki yaygın yolu vardır: parametreyi tekrarlamak (?rol=admin&rol=editor) veya değerleri virgülle ayırmak (?rol=admin,editor). FastAPI'de list[str] tipli bir Query parametresi ilk biçimi doğrudan destekler; virgüllü biçimde ise tek bir metin alırsınız ve onu kendiniz bölmeniz gerekir:

python
from fastapi import FastAPI, Query
from fastapi.testclient import TestClient

app = FastAPI()

@app.get("/kullanicilar")
def listele(rol: list[str] = Query(default=[])):
    return {"rol": rol}

c = TestClient(app)
print(c.get("/kullanicilar?rol=admin&rol=editor").json())
# çıktı: {'rol': ['admin', 'editor']}
print(c.get("/kullanicilar?rol=admin,editor").json())
# çıktı: {'rol': ['admin,editor']}
print(c.get("/kullanicilar").json())
# çıktı: {'rol': []}
print(c.get("/kullanicilar", params={"rol": "çağrı merkezi"}).request.url)
# çıktı: http://testserver/kullanicilar?rol=%C3%A7a%C4%9Fr%C4%B1+merkezi

Son satırda Türkçe karakterlerin URL'de yüzde kodlamasıyla (percent-encoding) UTF-8 baytları olarak, boşluğun ise bu istemcide + işaretiyle gönderildiğini görüyorsunuz. İstemci kütüphaneleri bunu kendiliğinden yapar; URL'yi elle birleştiren kodlarda ise kodlamayı unutmak bozuk isteklere yol açar. Hangi biçimi seçerseniz seçin, bütün uç noktalarda aynısını kullanın ve belgelerinize yazın.

Uzunluk ve güvenlik sınırları

Query parametreleri URL'nin parçası olduğu için uzunlukları sınırsız değildir. RFC 9110, göndericilerin ve alıcıların en az 8000 oktet uzunluğundaki URI'leri desteklemesini önerir; sunucu daha uzun bir hedef URI'yi işlemeyi reddederse 414 URI Too Long döner. RFC 9110'a göre bu kodun görüldüğü tipik durumlardan biri, aslında POST olması gereken bir isteğin uzun query bilgisiyle GET'e çevrilmesidir. Yüzlerce kimlik içeren bir filtre gönderiyorsanız bu, query yerine istek gövdesi kullanan bir tasarım ihtiyacının işaretidir.

İkinci sınır güvenlikle ilgilidir. RFC 9110, istekteki verilere güvenilemeyeceğini ve URI parametrelerinin doğrudan bir veritabanı sorgusuna eklenmesinin SQL enjeksiyonu (SQL injection) gibi saldırılara kapı açtığını anlatır; önerisi parametreleri sabit değerlerle karşılaştırıp sonucuna göre davranmaktır. Path ve query parametrelerini tipine göre doğrulamak, izin verilen değerleri bir beyaz listeyle sınırlamak ve sorgulara parametreli biçimde aktarmak bu yüzden tasarımın parçasıdır. API güvenliğinin genel çerçevesi için REST API güvenlik rehberi yazısına bakın.

Üçüncü bir nokta da güvenli metotlarla ilgilidir. RFC 9110, URI içindeki parametrelerin bir eylem seçtiği tasarımlarda (ör. page?do=delete) bu eylemin güvenli bir metotla, yani GET ile tetiklenememesini kaynak sahibinin sorumluluğu sayar. Query parametresiyle silme veya güncelleme yaptırmak, bağlantıları otomatik gezen araçların istemeden veri değiştirmesine yol açar. Eylemler HTTP metoduyla ifade edilir, query ile değil.

Sık yapılan hatalar

  • Filtreyi path'e koymak: /kullanicilar/rol/admin gibi yollar filtreyi kaynak gibi gösterir, birden çok filtreyi birleştirmeyi zorlaştırır ve 404 ile boş sonuç ayrımını bulanıklaştırır.
  • Kimliği query'ye koymak: /kullanici?id=42 tekil kaynağı koleksiyon görünümü gibi gösterir ve kaynak yoksa ne dönüleceğini belirsizleştirir.
  • Boş filtre sonucuna 404 dönmek: Koleksiyon vardır, yalnızca filtreye uyan öğe yoktur; doğru yanıt 200 ve boş listedir.
  • Hassas bilgiyi URL'ye yazmak: Anahtarlar ve kişisel veriler kayıt dosyalarında ve geçmişte kalır.
  • Query ile eylem tetiklemek: GET /siparisler/7?islem=iptal güvenli metodun anlamını bozar.
  • Tanınmayan parametreleri sessizce yok saymak: Yazım hatalı bir filtre tüm veriyi döndürebilir; davranışı belgeleyin veya reddedin.
  • Tutarsız çoklu değer biçimi: Bir uç noktada tekrar eden parametre, diğerinde virgüllü liste kullanmak istemci kodunu gereksiz yere karmaşıklaştırır.

Sık Sorulan Sorular

Sayfa numarası path'e konabilir mi?

Konabilir ama önerilmez. /kullanicilar/sayfa/2 sayfayı ayrı bir kaynak gibi gösterir. Sayfalama koleksiyonun bir görünümüdür ve ?page=2&limit=20 gibi query parametreleriyle ifade edilir.

Path parametresinde Türkçe karakter kullanabilir miyim?

URI içinde ASCII dışı karakterler yüzde kodlamasıyla taşınır, bu yüzden teknik olarak mümkündür. Yine de kimlik olarak sayısal veya ASCII bir değer kullanmak, kodlama hatalarını ve aynı ada farklı yazımların karışmasını önler.

Sürüm numarası path'e mi query'ye mi?

Yaygın bir tercih /v1/kullanicilar gibi path içinde sürümlemedir; başlıkla sürümleme de bir seçenektir. İki yaklaşımın artı ve eksileri için API versiyonlama yazısına bakın.

Arama terimi çok uzunsa ne yapmalıyım?

Kısa arama terimleri için ?q= uygundur. Karmaşık, çok ölçütlü ve uzun sorgularda 414 riskine karşı sorguyu POST ile istek gövdesinde gönderen ayrı bir arama uç noktası düşünülebilir. RFC 9110 bu yolun önbelleğe almayı zorlaştırdığını ve aslında güvenli olan bir istek için güvenli olmayan bir metot kullandığını da belirtir.

Marka adları sahiplerine aittir. Bu yazı bağımsız bir teknik rehberdir; adı geçen kuruluşlarla bayilik veya ortaklık ilişkisi bulunduğu anlamına gelmez.

İlgili içerikler: REST API tasarımı öğrenme rehberi · Filtreleme, sıralama ve arama · İç içe kaynaklar · Endpoint isimlendirme · Offset sayfalama · Cursor sayfalama · REST API güvenliği