REST API'de uç nokta (endpoint) adları için yaygın kural setinin özeti dört kelimedir: isim, çoğul, küçük harf, tutarlı. Kaynağı bir isimle adlandırırsınız (/kullanicilar, /getUsers değil), koleksiyonlar için çoğul kullanırsınız (/siparisler, /siparis değil), adresleri küçük harfle yazarsınız ve seçtiğiniz kalıbı API'nin her yerinde aynı biçimde uygularsınız. İşlemin ne olduğunu ise URL değil, HTTP metodu söyler.

Bu yazıda her kuralın gerekçesini ve sınırlarını ele alıyoruz: hangisi HTTP standardından gelir, hangisi tutarlılık için seçilmiş bir tercihtir. Türkçe karakterler, sondaki eğik çizgi, çok kelimeli adlar ve CRUD'a sığmayan işlemler gibi gerçek projelerde karşılaşılan durumlara da bakıyoruz. Kaynak kavramının kendisi REST API nedir yazısında anlatılıyor.

Kural 1: Kaynak bir isimdir, fiil değil

HTTP'de fiil görevini metot görür. RFC 9110, isteğin anlamının birincil kaynağının metot olduğunu ve standart metotların her kaynağa aynı anlamla uygulandığını belirtir. URL'ye fiil koymak bu yüzden aynı bilgiyi iki kez söylemek, üstelik ara katmanların anlamadığı bir yerde söylemek demektir. Karşılaştırın:

bash
# Eylem tabanlı (kaçının)
GET    /getUsers
POST   /createUser
POST   /updateUser
POST   /deleteUser

# Kaynak tabanlı
GET    /kullanicilar
POST   /kullanicilar
PUT    /kullanicilar/42
DELETE /kullanicilar/42

İkinci listede iki adres var ve dört işlemin hepsi bu iki adrese oturuyor. Yeni bir geliştirici /urunler adresini gördüğünde, belgeye bakmadan GET /urunler ile listeleyebileceğini ve POST /urunler ile oluşturabileceğini tahmin eder. Eylem tabanlı listede ise her yeni işlem yeni bir ad öğrenmeyi gerektirir.

Fiilin URL'ye sızdığı yerler bazen gözden kaçar. /kullanicilar/42/sil, /siparisler/olustur ve /urun-ara da aynı kalıbın Türkçe hâlleridir. Arama için kaynak tabanlı karşılık bir sorgu parametresidir: GET /urunler?q=klavye. Filtreleme ve arama parametreleri filtreleme, sıralama ve arama yazısında anlatılır.

Kural 2: Koleksiyonlar çoğul adlandırılır

Kural 2: Koleksiyonlar çoğul adlandırılır

Çoğul ad kullanmak HTTP'nin bir gereği değildir; yaygın bir tasarım tercihidir. Gerekçesi URL'lerin bir dosya yolu gibi okunmasıdır: /kullanicilar bütün kullanıcıların koleksiyonudur, /kullanicilar/42 bu koleksiyonun bir üyesidir. Koleksiyon ile üye aynı kökü paylaşınca istemci hiçbir istisna ezberlemez.

Tekil ve çoğulu karıştırmak en sık görülen tutarsızlıklardan biridir. Bir API'de /kullanici/42 ve /siparisler birlikte bulunuyorsa istemci her kaynak için hangi biçimin kullanıldığını ayrı ayrı öğrenmek zorunda kalır. Tekil adı tercih eden ekipler de vardır; önemli olan bir biçimi seçip her yerde uygulamaktır. Bu seride çoğul biçimi kullanıyoruz.

Gerçekten tek olan kaynaklar bu kuralın doğal istisnasıdır. Oturum açmış kullanıcının profili gibi istemci başına bir tane olan bir kaynak için /profil ya da /ben gibi tekil bir ad kullanmak anlamlıdır, çünkü ortada bir koleksiyon yoktur. Bu istisnayı bilinçli olarak ve belgeleyerek yapın.

Kural 3: Adresleri küçük harfle yazın

Bu kuralın arkasında doğrudan bir standart davranışı vardır. RFC 9110'un http ve https URI'lerini karşılaştırma kurallarına göre şema ve ana makine adı büyük-küçük harfe duyarsızdır ve normalde küçük harfle yazılır; geri kalan bütün bileşenler, yani yol da, büyük-küçük harfe duyarlı karşılaştırılır. API.EXAMPLE.COM ile api.example.com aynı makinedir, ama /Siparisler ile /siparisler iki farklı yoldur.

Sunucunuz bu iki yolu aynı kaynağa bağlamadığı sürece büyük harfle yazılmış bir istek 404 alır. Aşağıdaki örnekte bunu ve Türkçe karakterlerin URL'de nasıl göründüğünü birlikte görüyoruz:

python
from urllib.parse import quote

from fastapi import FastAPI
from fastapi.testclient import TestClient

print(quote("/siparişler"))  # çıktı: /sipari%C5%9Fler
print(quote("/siparisler"))  # çıktı: /siparisler

app = FastAPI()

@app.get("/siparisler")
def siparisler():
    return {"data": []}

c = TestClient(app)
print(c.get("/siparisler").status_code)  # çıktı: 200
print(c.get("/Siparisler").status_code)  # çıktı: 404

r = c.get("/siparisler/", follow_redirects=False)
print(r.status_code, r.headers["location"])  # çıktı: 307 http://testserver/siparisler

İlk iki satır, ş harfinin URL'de %C5%9F olarak yüzde kodlamasıyla (percent-encoding) taşındığını gösterir. İstemci kütüphaneleri bu kodlamayı genellikle kendisi yapar, ama günlük kayıtlarında, hata mesajlarında ve elle yazılan curl komutlarında kodlanmış hâl görünür ve okunması zorlaşır. Bu yüzden Türkçe kaynak adı kullanacaksanız ASCII karşılıklarını yazmak (siparisler, kullanicilar, urunler) pratik bir tercihtir. İngilizce adlar (/orders, /users) da aynı ölçüde yaygındır; bir API'de iki dili karıştırmamak ise ayrıca önemlidir.

Sondaki eğik çizgi: tek bir biçim seçin

Örneğin son satırı başka bir ayrıntıyı gösterir. FastAPI, rota /siparisler olarak tanımlıyken gelen /siparisler/ isteğini 307 Temporary Redirect ile eğik çizgisiz adrese yönlendirdi. Bu, FastAPI'nin varsayılan davranışıdır; başka çatılar aynı isteğe 404 dönebilir ya da iki adresi sessizce aynı kabul edebilir. RFC 9110'un normalleştirme kuralları boş yolu / ile eşit sayar ama /siparisler ile /siparisler/ adreslerini eşit saymaz; bunlar iki ayrı URI'dir.

Tasarım açısından yapılacak şey, bir biçim seçmek ve belgede, OpenAPI tanımında ve örneklerde hep onu kullanmaktır. Yönlendirme istemcinin ek bir istek daha yapmasına yol açar ve bazı istemciler yönlendirmeyi otomatik izlemez. En sade yol, sondaki eğik çizgisiz biçimi esas almaktır.

Çok kelimeli adlar

Kaynak adı iki kelimeden oluşuyorsa, örneğin sipariş kalemleri ya da teslimat adresleri, bir ayırıcı seçmeniz gerekir. Bu konuda bağlayıcı bir standart yoktur; sık görülen seçenekler kısa çizgi (/siparis-kalemleri) ve alt çizgidir (/siparis_kalemleri). Kısa çizgi URL'lerde okunması kolay olduğu için sık tercih edilir. Alt çizgiyi seçen ekipler ise genellikle JSON anahtarlarında ve sorgu parametrelerinde kullandıkları snake_case ile aynı görünümü korumak ister: created_after gibi bir sorgu parametresi zaten alt çizgilidir.

Hangisini seçerseniz seçin, camelCase biçimini (/siparisKalemleri) yol için seçmemenizi öneririz, çünkü yol büyük-küçük harfe duyarlıdır ve küçük harf kuralını bozar. JSON alan adlarında hangi biçimin neden seçildiği alan adlandırma yazısında anlatılır.

URL'de bulunmaması gerekenler

URL'de bulunmaması gerekenler: Fiiller, Gizli bilgiler, Uygulama ayrıntıları, Biçim uzantıları
  • Fiiller: /getUsers, /kullanici-sil. İşlemi metot söyler.
  • Gizli bilgiler: RFC 9110, URI'lerin paylaşılmak için tasarlandığını, ekranlarda gösterildiğini, yer imlerine eklendiğini ve sunucular ile vekil sunucular tarafından günlüklere yazıldığını hatırlatır; bu yüzden hassas ya da kişiyi tanımlayan bilgiyi URI'ye koymak akıllıca değildir. API anahtarı ya da token adreste değil Authorization başlığında taşınır; ayrıntılar API key yazısında.
  • Uygulama ayrıntıları: /api.php?tablo=kullanicilar gibi sunucu dilini ya da veritabanı tablosunu ele veren adlar, iç yapı değiştiğinde URL'yi de değiştirmeye zorlar.
  • Biçim uzantıları: /urunler/7.json gibi adresler temsili URL'ye bağlar; biçim seçimi için Accept başlığını kullanmak içerik anlaşması yazısında anlatılır.

CRUD'a sığmayan işlemler nasıl adlandırılır?

Her işlem doğrudan oluştur, oku, değiştir, sil kalıbına oturmaz. Bir siparişi iptal etmek, bir hesabı etkinleştirmek, bir şifreyi sıfırlamak gibi işlemler için kaynaklarda doğrudan bir kural yoktur; aşağıdaki seçenekler tasarım tercihleridir ve her birinin bir bedeli vardır.

Birinci seçenek, işlemi kaynağın durumunda bir değişiklik olarak modellemektir. Sipariş iptali, siparişin durum alanını değiştirmektir:

http
PATCH /siparisler/42 HTTP/1.1
Content-Type: application/json

{"durum": "iptal"}

Bu yaklaşım yeni bir adres gerektirmez ve kaynak modelini korur. Zayıf yanı, iptalin yan etkileri (iade başlatmak, stok geri eklemek) olduğunda bunların basit bir alan güncellemesinin arkasında saklanmasıdır. İkinci seçenek, işlemin kendisini bir kaynak olarak görmektir: iptal bir kayıt bırakıyorsa POST /siparisler/42/iptaller ya da POST /iadeler gibi bir koleksiyona yeni bir kayıt eklemek anlamlıdır. Burada ad hâlâ bir isimdir ve oluşturulan kaydı sonradan GET ile okuyabilirsiniz. Hangi seçeneği seçerseniz seçin, aynı türdeki bütün işlemler için aynı kalıbı kullanın.

Tutarlılık tablosu: kararları yazıya dökün

Tutarlılık tablosu: kararları yazıya dökün: Kaynak dili, Tekil ya da çoğul, Harf, Çok kelimeli ad

İsimlendirme kurallarının asıl değeri tutarlılıktır. Ekip büyüdükçe kararların yazılı olması önem kazanır. Mağaza API'si için verdiğimiz kararlar şöyle bir tabloya sığar:

KararSeçimÖrnek
Kaynak diliTürkçe, ASCII harflerle/siparisler
Tekil ya da çoğulKoleksiyonlar çoğul/urunler, /urunler/7
HarfYalnızca küçük harf/kullanicilar
Çok kelimeli adKısa çizgi/siparis-kalemleri
Sondaki eğik çizgiYok/siparisler
Sürüm önekiYolun başında/v1/siparisler
Kimlik biçimiYolda tek parça/siparisler/1001

Sürüm önekinin neden yolun başında durduğu ve başlıkla sürümlemenin farkı API versiyonlama yazısında ele alınır. Bu kararlarla mağaza API'sinin uç nokta listesi şöyle görünür:

bash
GET    /v1/urunler
POST   /v1/urunler
GET    /v1/urunler/7
PUT    /v1/urunler/7
DELETE /v1/urunler/7
GET    /v1/siparisler?durum=beklemede
POST   /v1/siparisler
GET    /v1/siparisler/1001
PATCH  /v1/siparisler/1001
GET    /v1/siparisler/1001/siparis-kalemleri

Listede tek bir fiil yok, her koleksiyon çoğul, her adres küçük harfli. Durum filtresi yolda değil sorgu dizesinde duruyor. Siparişin kalemleri ise siparişe bağlı bir alt koleksiyon olarak yazılmış; iç içe yolların ne kadar derin olabileceği iç içe kaynaklar yazısında anlatılıyor.

Örnek: kötü adlandırılmış bir API'yi düzeltmek

Kuralları bir arada görmek için zamanla büyümüş, kimsenin baştan planlamadığı bir uç nokta listesini ele alalım. Listede fiiller, tekil ve çoğul adlar, büyük harfler ve iki dil bir arada. Soldaki sütun mevcut adresi, sağdaki sütun aynı işlemin kaynak tabanlı karşılığını gösteriyor:

Mevcut uç noktaKaynak tabanlı karşılığıDüzeltilen sorun
GET /getAllProductsGET /urunlerFiil ve İngilizce ad
GET /Urun/7GET /urunler/7Büyük harf ve tekil ad
POST /urunEklePOST /urunlerFiil, camelCase
GET /aktifSiparislerGET /siparisler?durum=aktifFiltre yol adına gömülmüş
POST /siparis/iptal?id=42PATCH /siparisler/42 ve durum alanıFiil, kimlik sorgu dizesinde
GET /kullanici-bilgisi?token=abcGET /ben ve Authorization başlığıGizli bilgi adreste

Düzeltmeden sonra ortada iki koleksiyon (/urunler, /siparisler) ve bir tekil kaynak (/ben) kalıyor. Altı farklı ad öğrenmek yerine istemci iki kalıp öğreniyor ve gerisini metotlardan çıkarıyor.

URL bir sözleşmedir: adı sonradan değiştirmek

Adlandırma kararlarının en önemli özelliği kalıcı olmalarıdır. Bir uç noktayı yayına aldığınız anda istemciler o adresi koda yazar, yapılandırma dosyalarına koyar, belgelerine ekler. Adı sonradan değiştirmek, eski adresi kullanan herkesi bozan kırıcı bir değişikliktir. Bu yüzden isimlendirmeyi ilk sürümden önce oturtmak, sonradan düzeltmekten çok daha ucuzdur.

Yine de değişiklik kaçınılmazsa HTTP'nin yönlendirme kodları yardımcı olur. RFC 9110'a göre 301 Moved Permanently ve 308 Permanent Redirect, hedef kaynağa kalıcı olarak yeni bir URI atandığını bildirir ve sunucunun yeni adresi Location başlığında göndermesi beklenir. İkisi arasında API'ler için önemli bir fark vardır: belge, 301 alan bir istemcinin tarihsel nedenlerle sonraki isteği POST yerine GET ile yapabileceğini, bu istenmiyorsa 308 kullanılabileceğini belirtir. Gövdeli istekleri yönlendirecekseniz 308 metodu korur. Eski adresleri tamamen kapatma süreci için breaking change yazısına ve Deprecation ve Sunset başlıkları yazısına bakın.

Sık yapılan hatalar

  • Aynı API'de tekil ve çoğul adları karıştırmak: /kullanici/42 ile /siparisler bir arada.
  • Adreste büyük harf kullanmak: /Kullanicilar ve /kullanicilar iki farklı yoldur.
  • Türkçe ve İngilizce adları karıştırmak: /kullanicilar/42/orders.
  • Arama ve filtreyi yeni bir uç noktaya taşımak: /aktif-kullanicilar yerine /kullanicilar?durum=aktif.
  • Token ya da anahtarı sorgu dizesine koymak: adres günlüklere yazılır.
  • Belgede eğik çizgili, kodda eğik çizgisiz adres kullanmak.

Sık Sorulan Sorular

Kaynak adlarını Türkçe mi, İngilizce mi yazmalıyım?

İkisi de geçerlidir. API'yi kullanacak kitleye göre seçin ve tek dilde kalın. Türkçe seçerseniz URL'de yüzde kodlamasıyla uğraşmamak için ASCII harfleri kullanmak pratik olur.

URL'de kısa çizgi mi, alt çizgi mi kullanmalıyım?

Bağlayıcı bir standart yoktur. Kısa çizgi okunaklılık için, alt çizgi JSON anahtarlarıyla aynı görünüm için seçilir. Hangisini seçerseniz bütün API'de onu kullanın.

/api öneki gerekli mi?

Gerekli değildir. API ile web sitesi aynı alan adını paylaşıyorsa ayrım için /api öneki işe yarar; API kendi alt alan adındaysa (api.example.com gibi) genellikle gereksizdir.

Kimlik olarak sayı mı, metin mi kullanmalıyım?

Yol açısından fark yoktur; kimlik tek bir yol parçası olarak durur. Kimlik tipinin JSON'daki gösterimi alan adlandırma yazısında tartışılır.

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 · REST API nedir · İç içe kaynaklar · Path ve query parametresi · Tasarım hataları