---
title: 'URI Tasarımı'
source: 'https://academia.sh/tr/kurslar/api-tasarimi/uri-tasarimi'
course: 'Web API Tasarımı'
language: tr
updated: '2026-08-17T18:06:45+00:00'
license: 'CC BY-SA 4.0'
---

# URI Tasarımı

Kaynak adreslerinin yol düzeni: yol ile sorgu bölümünün işbölümü, koleksiyon ve tekil kaynak adlandırması, sabit dilimle parametre diliminin çakışması, iç içe yolların üst dilimi ve kanonik adres kararı.

Bir önceki ders kaynakları belirledi: kitap, üye, ödünç kaydı, şube ve tabloya karşılık
gelmeyen üye durumu. O dersin örneklerinde `/kitaplar`, `/oduncler/1` ve
`/uyeler/U-1001/durum` gibi adresler sorgulanmadan kullanıldı. Oysa her biri bir karardı ve
her kararın alternatifi vardı.

Bu ders o kararları açar. Adresin hangi parçası kaynağın kimliğini taşır, hangi parçası
görünümünü belirler; koleksiyon adı nasıl yazılır; bir ödünç kaydına iki farklı yoldan
başvurulabildiğinde hangisi geçerli sayılır; sondaki eğik çizgi neyi değiştirir. Bunların
hepsi, bir eşleyici tablosu çalıştırıldığında ölçülebilir sonuçlar üretir.

## Yolun ve Sorgunun İşbölümü

İnternet Nasıl Çalışır kursunda adresin bölümleri tanıtılmıştı: şema, konak, yol, sorgu ve
parça. API tasarımında bu bölümler arasında keskin bir işbölümü vardır.

**Yol kaynağın kimliğini taşır.** `/kitaplar/978-0131103627` belirli bir kitabı gösterir;
o kitap başka bir yolda bulunmaz. **Sorgu bölümü kaynağın görünümünü belirler.**
`/oduncler?durum=acik` yeni bir kaynak değildir, ödünç koleksiyonunun süzülmüş bir
görünümüdür. Aynı ayrım sıralama, sayfalama ve alan seçimi için de geçerlidir; bunların
hepsi sonraki derslerin konusudur ve hepsi sorgu bölümünde yaşar.

Bu işbölümünün pratik sonucu şudur: **yolda fiil bulunmaz.** `/oduncIade`, `/kitapAra`,
`/uyeSil` gibi adresler kaynağı değil işlemi adlandırır. Ne yapılacağını söyleyen şey
yöntemdir; yol yalnızca neye yapılacağını söyler. Bir sonraki ders yöntemlerin bu görevi
nasıl üstlendiğini ayrıntısıyla ele alır.

Koleksiyon adları çoğul yazılır: `/kitaplar`, `/uyeler`, `/oduncler`. Gerekçe estetik
değildir. `/kitap/978-0131103627` ile `/kitaplar` bir arada kullanıldığında istemci
tarafındaki her adres kurma noktası, tekil mi çoğul mu olduğunu ayrıca hatırlamak zorunda
kalır. Tek bir kural — koleksiyon çoğul, üyesi onun altında — bu hatırlama yükünü sıfırlar.
Hangi kuralın seçildiği ikincildir; kurs boyunca değişmemesi birincildir.

## Sabit Dilim ile Parametre Diliminin Çakışması

Adres kalıpları bir tablodur ve tablo sırayla taranır. Sıra yanlış olduğunda hata
sessizdir. Aşağıdaki eşleyici, hangi kalıbın eşleştiğini yanıtın içinde bildirir; böylece
karar görünür olur.

```js
// yol-esleyici.mjs — adres kaliplarini sirayla deneyen esleyici; eslesen kalibi bildirir
import { createServer } from "node:http";
import { DatabaseSync } from "node:sqlite";

const db = new DatabaseSync("kutuphane.db");
const yanitla = (yanit, kod, nesne) => {
  yanit.writeHead(kod, { "content-type": "application/json; charset=utf-8" });
  yanit.end(JSON.stringify(nesne));
};

// Sira onemlidir: liste yukaridan asagi taranir, ilk eslesen kazanir.
const KALIPLAR = [
  ["/kitaplar/{isbn}", /^\/kitaplar\/([^/]+)$/, (e) =>
    db.prepare("SELECT * FROM kitap WHERE isbn = ?").get(e[1]) ?? null],
  ["/kitaplar/ara",    /^\/kitaplar\/ara$/, () =>
    db.prepare("SELECT isbn, ad FROM kitap WHERE yazar LIKE 'K%'").all()],
  ["/uyeler/{kod}/oduncler/{id}", /^\/uyeler\/([^/]+)\/oduncler\/(\d+)$/, (e) =>
    db.prepare("SELECT * FROM odunc WHERE id = ?").get(Number(e[2])) ?? null],
];

const sunucu = createServer((istek, yanit) => {
  const yol = new URL(istek.url, "http://127.0.0.1").pathname;
  for (const [ad, kalip, isleyici] of KALIPLAR) {
    const eslesme = kalip.exec(yol);
    if (eslesme) return yanitla(yanit, 200, { eslesen: ad, sonuc: isleyici(eslesme) });
  }
  yanitla(yanit, 404, { hata: "yol_yok" });
});

sunucu.listen(8474, "127.0.0.1", () => console.log("yol esleyici 127.0.0.1:8474"));
```

Şema, bir önceki derste kurulan `sema.sql` dosyasıdır; betik veritabanını her çalıştırmada
yeniden üretir.

```bash
rm -f kutuphane.db && sqlite3 kutuphane.db < sema.sql
node yol-esleyici.mjs & sunucu=$!
sleep 0.4

iste() { printf '%-34s %s\n' "$1" "$(curl -sS "http://127.0.0.1:8474$1")"; }
iste "/kitaplar/978-0131103627"
iste "/kitaplar/ara"
iste "/uyeler/U-1002/oduncler/1"
iste "/kitaplar/978-0131103627/"
iste "/Kitaplar/978-0131103627"

kill $sunucu
```

```
yol esleyici 127.0.0.1:8474
/kitaplar/978-0131103627           {"eslesen":"/kitaplar/{isbn}","sonuc":{"isbn":"978-0131103627","ad":"C Programlama Dili","yazar":"Ritchie","yil":1978,"sube":"S-01"}}
/kitaplar/ara                      {"eslesen":"/kitaplar/{isbn}","sonuc":null}
/uyeler/U-1002/oduncler/1          {"eslesen":"/uyeler/{kod}/oduncler/{id}","sonuc":{"id":1,"uye":"U-1001","isbn":"978-0262033848","verilis":"2026-03-01","iade":null}}
/kitaplar/978-0131103627/          {"hata":"yol_yok"}
/Kitaplar/978-0131103627           {"hata":"yol_yok"}
```

Beş satırda dört ayrı sorun görünür.

İkinci satır çakışmadır. `/kitaplar/ara` adresi arama kalıbına hiç ulaşmadı; parametre
dilimi olan `{isbn}` onu önce yakaladı ve `ara` dizgisini ISBN sanıp veritabanında aradı.
Sonuç `null` döndü — yani istemci, arama uç noktasının bozuk olduğunu değil, aradığı
kitabın bulunmadığını düşünür. Uygulama Mimarisi kursunda tanımlanan **özgüllük** ölçütü
burada da geçerlidir: sabit dilimli kalıplar parametre dilimli kalıplardan önce
denenmelidir. Sıraya güvenmek istemiyorsanız ikinci yol vardır — arama ölçütünü koleksiyon
adresinin sorgu bölümüne taşımak: `/kitaplar?yazar=Knuth`. Bu yazımda çakışacak bir dilim
kalmaz.

Üçüncü satır daha ağırdır. `/uyeler/U-1002/oduncler/1` adresine U-1001 üyesinin ödünç
kaydı döndü. Kalıp üst dilimi yakaladı ama işleyici onu hiç kullanmadı; yalnızca
`{id}` ile sorgu yaptı. İç içe yol burada süsleme durumuna düşmüştür: adres bir sahiplik
iddiası taşır, sunucu o iddiayı denetlemez.

Dördüncü ve beşinci satırlar kanonik biçim sorunudur. Sondaki eğik çizgi ve büyük harf,
yolun eşleşmemesine yol açtı. İkisi de "bulunamadı" olarak döndü, oysa kaynak duruyor.

## Üst Dilimin Doğrulanması ve Kanonik Biçim

Aşağıdaki eşleyici üç düzeltmeyi birlikte uygular: sabit dilim öne alınır, üst dilim sorguya
katılır, sondaki eğik çizgi kanonik adrese yönlendirilir.

```js
// yol-esleyici-2.mjs — ozgullugu once sinayan, ust dilimi dogrulayan esleyici
import { createServer } from "node:http";
import { DatabaseSync } from "node:sqlite";

const db = new DatabaseSync("kutuphane.db");
const yanitla = (yanit, kod, nesne) => {
  yanit.writeHead(kod, { "content-type": "application/json; charset=utf-8" });
  yanit.end(JSON.stringify(nesne));
};

// Sabit dilimli kaliplar once; parametre dilimi en sonda kalir.
const KALIPLAR = [
  ["/kitaplar/ara",    /^\/kitaplar\/ara$/, () =>
    [200, db.prepare("SELECT isbn, ad FROM kitap WHERE yazar LIKE 'K%'").all()]],
  ["/kitaplar/{isbn}", /^\/kitaplar\/([^/]+)$/, (e) => {
    const k = db.prepare("SELECT * FROM kitap WHERE isbn = ?").get(e[1]);
    return k ? [200, k] : [404, { hata: "kitap_yok" }];
  }],
  ["/uyeler/{kod}/oduncler/{id}", /^\/uyeler\/([^/]+)\/oduncler\/(\d+)$/, (e) => {
    // Ust dilim suslemedir demek yanlistir: kayit o uyeye ait degilse yol yoktur.
    const o = db.prepare("SELECT * FROM odunc WHERE id = ? AND uye = ?").get(Number(e[2]), e[1]);
    return o ? [200, o] : [404, { hata: "odunc_yok" }];
  }],
];

const sunucu = createServer((istek, yanit) => {
  let yol = new URL(istek.url, "http://127.0.0.1").pathname;
  // Kanonik bicim: sondaki egik cizgi tek adrese indirgenir.
  if (yol.length > 1 && yol.endsWith("/")) {
    yanit.writeHead(308, { location: yol.slice(0, -1) });
    return yanit.end();
  }
  for (const [ad, kalip, isleyici] of KALIPLAR) {
    const eslesme = kalip.exec(yol);
    if (eslesme) {
      const [kod, sonuc] = isleyici(eslesme);
      return yanitla(yanit, kod, { eslesen: ad, sonuc });
    }
  }
  yanitla(yanit, 404, { hata: "yol_yok" });
});

sunucu.listen(8475, "127.0.0.1", () => console.log("duzeltilmis esleyici 127.0.0.1:8475"));
```

```bash
rm -f kutuphane.db && sqlite3 kutuphane.db < sema.sql
node yol-esleyici-2.mjs & sunucu=$!
sleep 0.4

iste() { printf '%-30s %3s  %s\n' "$1" \
  "$(curl -sS -o /dev/null -w '%{http_code}' "http://127.0.0.1:8475$1")" \
  "$(curl -sS "http://127.0.0.1:8475$1")"; }
iste "/kitaplar/ara"
iste "/uyeler/U-1002/oduncler/1"
iste "/uyeler/U-1001/oduncler/1"
iste "/kitaplar/978-0131103627/"

# Ayni adres, yonlendirme izlenerek:
printf '%-30s %3s  %s\n' "/kitaplar/978-0131103627/ (-L)" \
  "$(curl -sSL -o /dev/null -w '%{http_code}' http://127.0.0.1:8475/kitaplar/978-0131103627/)" \
  "$(curl -sSL http://127.0.0.1:8475/kitaplar/978-0131103627/)"

kill $sunucu
```

```
duzeltilmis esleyici 127.0.0.1:8475
/kitaplar/ara                  200  {"eslesen":"/kitaplar/ara","sonuc":[{"isbn":"978-0201896831","ad":"Programlama Sanati"}]}
/uyeler/U-1002/oduncler/1      404  {"eslesen":"/uyeler/{kod}/oduncler/{id}","sonuc":{"hata":"odunc_yok"}}
/uyeler/U-1001/oduncler/1      200  {"eslesen":"/uyeler/{kod}/oduncler/{id}","sonuc":{"id":1,"uye":"U-1001","isbn":"978-0262033848","verilis":"2026-03-01","iade":null}}
/kitaplar/978-0131103627/      308
/kitaplar/978-0131103627/ (-L) 200  {"eslesen":"/kitaplar/{isbn}","sonuc":{"isbn":"978-0131103627","ad":"C Programlama Dili","yazar":"Ritchie","yil":1978,"sube":"S-01"}}
```

İkinci ve üçüncü satır birlikte okunmalıdır: aynı ödünç kaydı, iki farklı üst dilimle iki
farklı sonuç verir. Üst dilim artık iddia değil koşuldur. Bu, güvenlik değil doğruluk
kararıdır; yetkilendirme ayrı bir konudur ve Kimlik Doğrulama ve Yetkilendirme kursunda ele
alınır. Buradaki kazanç şudur: adres neyi söylüyorsa sunucu onu doğrular.

Dördüncü satır yönlendirmeyi gösteriyor: sondaki eğik çizgili adres 308 ile kanonik adrese
çevrilir, gövde boştur. Beşinci satırda aynı istek yönlendirme izlenerek yapılır ve 200
ile kaynağa ulaşır. Yönlendirmenin bu türü yöntemi ve gövdeyi korur; bu ayrım, yazma
isteklerinin yönlendirilmesi gerektiğinde belirleyici olur.

Büyük harf sorunu bilinçli olarak düzeltilmedi. Yol büyük–küçük harfe duyarlıdır ve
duyarlı kalmalıdır; `/Kitaplar` yolunu da kabul etmek, aynı kaynak için sonsuz sayıda
adres üretmek demektir. Doğru karar tek yazımı belgelemek ve diğerlerini reddetmektir.

## Kimliğin İçindeki Ayraçlar

Yol dilimlerini ayıran karakter eğik çizgidir; kimlik değeri o karakteri içeriyorsa adres
bozulur.

```bash
# Kimlikte yol ayraci gecerse ne olur?
node -e '
const kod = "S-01/Merkez";
console.log("ham        :", "/subeler/" + kod);
console.log("kodlanmis  :", "/subeler/" + encodeURIComponent(kod));
const adres = new URL("http://127.0.0.1/subeler/" + encodeURIComponent(kod));
console.log("cozulen    :", decodeURIComponent(adres.pathname.split("/")[2]));
'
```

```
ham        : /subeler/S-01/Merkez
kodlanmis  : /subeler/S-01%2FMerkez
cozulen    : S-01/Merkez
```

Ham yazımda tek bir kimlik iki dilime bölünür ve eşleyici onu alt kaynak sanır. Kodlanmış
yazımda dilim bütünlüğünü korur. Bu, istemcinin sorumluluğudur ve sözleşmede yazılı olmalıdır.
Daha sağlam olan yol ise kimliğin biçimini baştan kısıtlamaktır: ayraç, boşluk ve büyük harf
içermeyen kimlikler bu sorunu hiç doğurmaz.

## Kanonik Adres Kararı

Bir kaynağa birden çok yoldan ulaşılabilir: `/oduncler/1` ve `/uyeler/U-1001/oduncler/1`
aynı kaydı verir. İkisi de geçerli olabilir, ama biri **kanonik adres** olarak seçilmelidir.
Seçim ölçütü şudur: kaynağın bağımsız kimliği varsa kanonik adres kısa olandır. Ödünç
kaydının kendi kimliği vardır, dolayısıyla `/oduncler/1` kanoniktir; iç içe yazım, üyenin
kayıtlarını gezerken kullanılan bir kısayoldur.

Kanonik adresin belirlenmesi üç yerde iş görür. Yanıt içinde bir kaynağa bağlantı
verilirken hangi biçimin yazılacağı bellidir. Yeni bir kayıt oluşturulduğunda `Location`
başlığına konacak adres bellidir. Önbellek anahtarı olarak adres kullanıldığında aynı
kaynak iki farklı anahtarla saklanmaz.

Derinlik de bu ölçütle sınırlanır. `/subeler/S-01/uyeler/U-1001/oduncler/1` gibi dört
düzeyli bir yol, dört ayrı doğrulama gerektirir ve dördü de her istekte yapılmalıdır. Kural
olarak bir düzey yeterlidir: koleksiyonun sahibi olan kaynak, sonra koleksiyon, sonra üyesi.
Daha derini gerekiyorsa bu, ilişkinin sorgu ölçütü olarak yazılmasının daha uygun olduğunun
işaretidir.

## Özet

- Yol kaynağın kimliğini taşır, sorgu bölümü görünümünü belirler; yolda fiil bulunmaz,
  ne yapılacağını yöntem söyler.
- Koleksiyonlar çoğul adlandırılır ve bu karar kurs boyunca değişmez; tutarlılık,
  hangi kuralın seçildiğinden daha önemlidir.
- Sabit dilimli kalıplar parametre dilimlilerden önce denenmelidir; aksi hâlde
  `/kitaplar/ara` adresi ISBN sanılır ve hata "bulunamadı" gibi görünür.
- İç içe yolun üst dilimi süsleme değil koşuldur: kayıt o üyeye ait değilse yanıt 404
  olmalıdır.
- Her kaynağın tek bir kanonik adresi belirlenir; sondaki eğik çizgi gibi türevler 308 ile
  ona yönlendirilir, büyük harf türevleri hiç kabul edilmez.
- Kimlik değerleri yol ayracı içeriyorsa adres kodlanmalıdır; daha sağlam çözüm kimlik
  biçimini baştan kısıtlamaktır.

## Sonraki Adım

Adresler yerleştiğine göre geriye eylemin nereden geldiği sorusu kalır. Bu derste yol
düzeninin fiil taşımadığı söylendi ve "ne yapılacağını yöntem söyler" denip geçildi. Ancak
yöntem seçmek bir ad seçmek değildir: her yöntemin, istemcinin ve aradaki bileşenlerin
güvendiği özellikleri vardır. Bir okumanın veriyi değiştirmediği, aynı isteğin iki kez
gönderilmesinin tek etki ürettiği varsayımları buradan gelir. Sonraki ders bu özellikleri
tanımlar ve her birini veritabanındaki satır sayısını sayarak sınar.
