Node.js'te bir adresi ayrıştırmak, değiştirmek ve yeniden oluşturmak için tarayıcılarla aynı WHATWG URL standardını uygulayan URL sınıfını kullanırsınız; sorgu parametrelerini okumak ve yazmak için de URLSearchParams vardır. İkisi de global nesnede hazırdır, import gerekmez. Geçersiz bir adres new URL() ile ERR_INVALID_URL kodlu bir TypeError fırlatır; hata fırlatmadan denemek için URL.canParse() ve URL.parse() statik metotları kullanılır. Eski url.parse() fonksiyonu ise belgelerde deprecated ve v24.0.0'dan beri uygulama kodunda çalışma zamanı uyarısı veriyor.
Bu yazıda URL nesnesinin alanlarını, göreli adres çözümlemenin inceliklerini, URLSearchParams metotlarını, yüzde kodlama farklarını ve node:url modülündeki fileURLToPath, pathToFileURL, url.format ve urlToHttpOptions yardımcılarını çalışan örneklerle anlatıyoruz. Örnekler Node.js v26.13.0 ile çalıştırıldı; dosya yolu örneklerinde windows seçeneğini açıkça verdiğimiz için çıktılar her işletim sisteminde aynıdır.
URL nesnesinin alanları

new URL(adres) adresi bileşenlerine ayırır ve her bileşeni bir alan olarak sunar. Belgelere göre bu alanların hepsi sınıfın prototipinde tanımlı alıcı/ayarlayıcı (getter/setter) çiftleridir; bu yüzden delete url.pathname gibi bir silme işlemi hiçbir etki yapmaz ama yine de true döndürür. Bir alanı değiştirdiğinizde href otomatik olarak yeniden oluşturulur:
// u1.mjs
const u = new URL('https://kullanici:gizli@shop.example.com:8443/urunler/ayakkabi?renk=siyah&beden=42#yorumlar');
console.log(u.protocol, u.username, u.password);
console.log(u.host, '|', u.hostname, '|', u.port);
console.log(u.pathname, u.search, u.hash);
console.log(u.origin);
u.port = '443';
console.log(u.port === '', u.host);
u.pathname = '/kategori/çanta';
console.log(u.pathname);
console.log(JSON.stringify({ adres: new URL('/a', 'https://example.org') }));
// çıktı: https: kullanici gizli
// çıktı: shop.example.com:8443 | shop.example.com | 8443
// çıktı: /urunler/ayakkabi ?renk=siyah&beden=42 #yorumlar
// çıktı: https://shop.example.com:8443
// çıktı: true shop.example.com
// çıktı: /kategori/%C3%A7anta
// çıktı: {"adres":"https://example.org/a"}Bu çıktıdan çıkan birkaç kural var. protocol sondaki iki noktayla gelir (https:). host port numarasını içerir, hostname içermez. origin protokol ve host'u kapsar ama belgelerin vurguladığı gibi kullanıcı adı ile parolayı kapsamaz. Port alanı protokolün varsayılan portuna ayarlanırsa boş dizeye döner; HTTPS için 443 bu yüzden adresten kayboldu. Yol alanına yazılan ç gibi geçersiz karakterler yüzde kodlanır. JSON.stringify() ise URL nesnesinin toJSON() metodunu çağırır ve adresin dize halini yazar.
| Alan | Örnekteki değer | Not |
|---|---|---|
| href | Adresin tamamı | Atama yeni bir URL ayrıştırır; geçersizse TypeError |
| protocol | https: | Özel ve özel olmayan şemalar arasında geçiş yapılamaz |
| username / password | kullanici / gizli | origin içinde yer almaz |
| host | shop.example.com:8443 | Port dahil |
| hostname | shop.example.com | Port hariç; Unicode adlar Punycode olur |
| port | 8443 | Varsayılan port boş dize olur |
| pathname | /urunler/ayakkabi | Geçersiz karakterler yüzde kodlanır |
| search | ?renk=siyah&beden=42 | Başında ? ile |
| searchParams | URLSearchParams nesnesi | Salt okunur alan; nesne URL'yi değiştirebilir |
| hash | #yorumlar | Başında # ile |
| origin | https://shop.example.com:8443 | Salt okunur |
Port ataması için belgeler ilginç kurallar listeliyor: değer önce dizeye çevrilir; geçersiz ama bir sayıyla başlayan bir dize verilirse baştaki sayı kullanılır ('5678abcd' için 5678), tamamen geçersiz değerler ve 0 ile 65535 aralığının dışındaki sayılar yok sayılır. Protokol değişikliğinde de bir kısıt var: WHATWG standardının "özel" saydığı şemalar (ftp, file, http, https, ws, wss) birbirine çevrilebilir, ama özel bir şemadan özel olmayan bir şemaya ya da tersine geçiş sessizce yok sayılır.
Göreli adresleri bir taban adrese göre çözmek
new URL(girdi, taban) biçimi, göreli bir girdiyi tarayıcının bağlantıları çözdüğü gibi taban adrese göre tamamlar. Girdi mutlaksa taban yok sayılır. Sonucu en çok etkileyen ayrıntı, taban adresin sonundaki eğik çizgidir:
// u2.mjs
console.log(new URL('/api/v1/users', 'https://example.org/eski/yol').href);
console.log(new URL('users', 'https://example.org/api/v1/').href);
console.log(new URL('users', 'https://example.org/api/v1').href);
console.log(new URL('https://baska.site/x', 'https://example.org/').href);
console.log(new URL('//cdn.example.net/app.js', 'https://example.org/').href);
console.log(new URL('https://çiçek.example').hostname);
// çıktı: https://example.org/api/v1/users
// çıktı: https://example.org/api/v1/users
// çıktı: https://example.org/api/users
// çıktı: https://baska.site/x
// çıktı: https://cdn.example.net/app.js
// çıktı: xn--iek-1lab.exampleTaban /api/v1 ile bittiğinde son parça bir "dosya" gibi değerlendirilip yerine users konuldu ve sonuç /api/users oldu. Bir API istemcisi yazarken taban adresi her zaman eğik çizgiyle bitirmek ve göreli yolları eğik çizgisiz yazmak bu sürprizi önler. Dördüncü ve beşinci satırlar güvenlik açısından önemli: girdi mutlak bir adres ya da // ile başlayan protokolden bağımsız bir adres olduğunda taban tamamen yok sayılır. Belgeler, girdinin mutlak olup olmadığını önceden bilmediğiniz durumlarda sonucun origin değerinin beklediğiniz gibi olduğunu doğrulamanızı öneriyor. Kullanıcıdan gelen bir "geri dönüş adresi" parametresini bu denetim olmadan yönlendirmede kullanmak açık yönlendirme (open redirect) açığına yol açar. Son satır da Unicode alan adlarının Punycode algoritmasıyla ASCII biçimine çevrildiğini gösteriyor.
Geçersiz adresler: TypeError, URL.canParse ve URL.parse

new URL() ayrıştıramadığı bir girdide TypeError fırlatır. Hatanın code alanı ERR_INVALID_URL olur ve belgelere göre hata nesnesi genellikle ayrıştırılamayan değeri taşıyan bir input alanı içerir. Her denemeyi try...catch ile sarmak istemiyorsanız iki statik metot var: v19.9.0 ve v18.17.0 ile gelen URL.canParse() yalnızca true/false döndürür; v22.1.0 ile gelen URL.parse() ise başarılıysa URL nesnesini, başarısızsa null döndürür:
// u3.mjs
try {
new URL('/sadece/yol');
} catch (err) {
console.log(err.name, err.code, err.input);
}
console.log(URL.canParse('/sadece/yol'), URL.canParse('/sadece/yol', 'https://example.org'));
console.log(URL.parse('bozuk adres'));
console.log(URL.parse('?q=1', 'https://example.org/ara').href);
// çıktı: TypeError ERR_INVALID_URL /sadece/yol
// çıktı: false true
// çıktı: null
// çıktı: https://example.org/ara?q=1/sadece/yol tek başına geçersiz, çünkü göreli bir girdi için taban gerekir; taban verildiğinde aynı girdi geçerli hale geliyor. Form verisinden ya da yapılandırma dosyasından gelen adresleri doğrularken URL.parse() sonucunu kontrol edip ardından protocol alanının http: veya https: olduğuna bakmak, hem geçersiz hem de istenmeyen şemaları (javascript:, file:) tek adımda eler.
URLSearchParams ile sorgu parametreleri
url.searchParams alanı, adresin sorgu kısmına bağlı canlı bir URLSearchParams nesnesidir; bu nesnede yaptığınız değişiklik doğrudan adrese yansır. get() ilk değeri ya da null, getAll() bütün değerleri bir dizi olarak döndürür. append() yeni bir çift ekler; set() ise aynı adlı ilk çiftin değerini değiştirip geri kalanları siler. delete() ve has() v20.2.0 ve v18.18.0'dan beri isteğe bağlı ikinci bir value argümanı da alıyor; size toplam çift sayısını verir:
// u4.mjs
const u = new URL('https://example.org/ara?q=node+js&etiket=api&etiket=http');
const s = u.searchParams;
console.log(s.get('q'), s.get('etiket'), s.getAll('etiket'), s.get('yok'));
console.log(s.has('etiket', 'http'), s.size);
s.append('sayfa', '2');
s.set('etiket', 'güvenlik');
s.delete('q');
console.log(u.search);
const yeni = new URLSearchParams({ ad: 'Ali Veli', not: 'a&b=c', liste: ['x', 'y'] });
console.log(yeni.toString());
console.log(Object.fromEntries(new URLSearchParams('a=1&b=2&a=3')));
for (const [k, v] of new URLSearchParams('?x=1&y=2')) console.log(k, v);
// çıktı: node js api [ 'api', 'http' ] null
// çıktı: true 3
// çıktı: ?etiket=g%C3%BCvenlik&sayfa=2
// çıktı: ad=Ali+Veli¬=a%26b%3Dc&liste=x%2Cy
// çıktı: { a: '3', b: '2' }
// çıktı: x 1
// çıktı: y 2Çıktıda dikkat edilecek noktalar şunlar. Sorgu dizesindeki + işareti okunurken boşluğa çevrildi (node js). Nesneden oluşturulan URLSearchParams değerleri dizeye çevirir; belgelere göre diziler tekrarlanan anahtarlara dönüşmez, Array.toString() ile virgülle birleştirilir ve sonuç x%2Cy olur. Tekrarlanan anahtar istiyorsanız [['liste', 'x'], ['liste', 'y']] gibi bir çift dizisi verin; belgeler her çiftin tam iki öğeden oluşması gerektiğini, aksi halde ERR_INVALID_TUPLE hatası alınacağını belirtiyor. Object.fromEntries() ise tekrarlanan anahtarlarda son değeri tutar; a için '1' kayboldu.
Yüzde kodlama: URL ile URLSearchParams farklı kodlar
Belgeler, WHATWG standardının hangi karakterin nerede kodlanacağını adresin bölümüne göre ayrı "yüzde kodlama kümeleri" ile belirlediğini anlatıyor (C0 kontrol, fragment, path ve userinfo kümeleri). Pratikteki sonuç şu: aynı karakter URL nesnesinin search alanına doğrudan yazıldığında ve URLSearchParams üzerinden yazıldığında farklı kodlanabilir. Belgelerdeki örnek tilde (~) karakteri; searchParams üzerinden yapılan herhangi bir değişiklik bütün sorguyu yeniden yazdığı için karakter bir anda kodlanmış hale geliyor:
// u5.mjs
const u = new URL('https://example.org/yol?dosya=~yedek');
console.log(u.search);
u.searchParams.sort();
console.log(u.search);
console.log(encodeURIComponent('a b'), new URLSearchParams({ q: 'a b' }).toString());
// çıktı: ?dosya=~yedek
// çıktı: ?dosya=%7Eyedek
// çıktı: a%20b q=a+bSon satır da sık karşılaşılan bir farkı gösteriyor: encodeURIComponent() boşluğu %20, URLSearchParams ise form kodlaması kuralıyla + olarak yazar. İkisi de sunucu tarafında doğru çözülür, ama imzalı adres üretirken ya da iki adresi dize olarak karşılaştırırken bu fark sorun çıkarır. İmza hesaplayacaksanız adresi tek bir yöntemle oluşturun; HMAC ile imza üretmek için crypto hash ve HMAC yazısına bakabilirsiniz.
Dosya yolları ve file: URL'leri: fileURLToPath, pathToFileURL
ES modüllerinde import.meta.url bir file: URL'si olduğu için yol ile URL arasında çeviri sık gerekir. url.fileURLToPath() yüzde kodlarını doğru çözer ve platforma uygun mutlak bir yol üretir; url.pathToFileURL() ise yolu mutlak hale getirip URL'de özel anlamı olan karakterleri (#, %, boşluk) kodlar. v22.1.0 ve v20.13.0 ile ikisine de windows seçeneği eklendi; true Windows, false POSIX kurallarını zorlar, verilmezse sistemin varsayılanı kullanılır:
// u6.mjs
import { fileURLToPath, pathToFileURL } from 'node:url';
console.log(fileURLToPath('file:///home/ayse/proje%20dosyalari/app.js', { windows: false }));
console.log(fileURLToPath('file:///C:/proje/src/app.js', { windows: true }));
console.log(pathToFileURL('/home/ayse/notlar #1.md', { windows: false }).href);
console.log(pathToFileURL('C:\\proje\\rapor %50.txt', { windows: true }).href);
console.log(new URL('file:///home/ayse/proje%20dosyalari/app.js').pathname);
console.log(fileURLToPath('file:///srv/app/%2e%2e/%2e%2e/etc/passwd', { windows: false }));
// çıktı: /home/ayse/proje dosyalari/app.js
// çıktı: C:\proje\src\app.js
// çıktı: file:///home/ayse/notlar%20%231.md
// çıktı: file:///C:/proje/rapor%20%2550.txt
// çıktı: /home/ayse/proje%20dosyalari/app.js
// çıktı: /etc/passwdBeşinci satır pathname alanının neden yol yerine kullanılamayacağını gösteriyor: yüzde kodları çözülmüyor. Altıncı satır ise belgelerdeki güvenlik uyarısının canlı örneği: fileURLToPath() kodlanmış nokta parçalarını (%2e%2e) çözüp yolu normalleştirdiği için gerçek bir üst klasöre çıkış üretiyor. Belgeler, uygulamaların dizin geçişi saldırılarına karşı yalnızca bu fonksiyona güvenmemesi gerektiğini, dönen yolun beklenen sınırlar içinde kaldığını ayrıca denetlemesi gerektiğini açıkça yazıyor. Bu denetimin bir örneği path modülü yazısındaki güvenli yol fonksiyonunda var. ESM'de import.meta.filename kullanımı için de import.meta yazısına bakabilirsiniz.
url.format, urlToHttpOptions ve eski url.parse
URL nesnesinin href ve toString() çıktısı özelleştirilemez. url.format(URL, seçenekler) ise kimlik bilgilerini (auth), parçayı (fragment) ya da sorguyu (search) çıkarmanıza ve unicode: true ile alan adını Punycode yerine Unicode yazmanıza izin verir. Adresleri günlüğe (log) yazmadan önce parolayı temizlemenin kolay yolu budur. Aynı örnekte eski url.parse() çağrısının ürettiği uyarıyı da görüyorsunuz:
// u7.mjs
import url from 'node:url';
const u = new URL('https://ali:sifre@example.org/panel?sekme=1#ozet');
console.log(url.format(u, { auth: false, fragment: false }));
console.log(url.format(u, { search: false }));
const eski = url.parse('https://example.org/a?b=1');
console.log(eski.pathname, eski.query);
// çıktı: https://example.org/panel?sekme=1
// çıktı: https://ali:sifre@example.org/panel#ozet
// çıktı: /a b=1
// stderr: (node:69108) [DEP0169] DeprecationWarning: `url.parse()` behavior is not standardized and prone to errors that have security implications. Use the WHATWG URL API instead. CVEs are not issued for `url.parse()` vulnerabilities.
// stderr: (Use `node --trace-deprecation ...` to show where the warning was created)Belgelere göre url.parse() standart dışı ve gevşek bir ayrıştırma algoritması kullanıyor; host adı sahteciliği gibi güvenlik sorunlarına açık ve bu fonksiyondaki açıklar için CVE yayınlanmıyor. DEP0169 kaydına göre v24.0.0 ile uyarı türü "Application" oldu: uyarı yalnızca node_modules dışındaki, yani sizin kodunuzdan yapılan çağrılarda basılıyor. url.resolve() ve dize argümanlı url.format() da içeride url.parse() çağırdığı için aynı kapsamda. Belgeler otomatik geçiş için npx codemod@latest @nodejs/node-url-to-whatwg-url komutunu gösteriyor. Deprecation türleri ve uyarıları yönetmek için deprecation uyarıları yazısına bakın.
url.urlToHttpOptions() ise bir URL nesnesini http.request() ve https.request() fonksiyonlarının beklediği seçenek nesnesine çevirir; port sayıya dönüşür, kimlik bilgileri auth alanına taşınır:
// u8.mjs
import { urlToHttpOptions } from 'node:url';
console.log(urlToHttpOptions(new URL('http://admin:123@localhost:3000/durum?ayrinti=1')));
// çıktı: [Object: null prototype] {
// çıktı: protocol: 'http:',
// çıktı: hostname: 'localhost',
// çıktı: hash: '',
// çıktı: search: '?ayrinti=1',
// çıktı: pathname: '/durum',
// çıktı: path: '/durum?ayrinti=1',
// çıktı: href: 'http://admin:123@localhost:3000/durum?ayrinti=1',
// çıktı: port: 3000,
// çıktı: auth: 'admin:123'
// çıktı: }Bir HTTP sunucusunda gelen isteğin adresini çözmek için belgelerin önerdiği kalıp, req.url değerini sabit bir taban adresle birleştirmektir: new URL(req.url, 'https://example.com'). Ters vekil (reverse proxy) arkasındaysanız belgeler x-forwarded-proto ve x-forwarded-host başlıklarının yalnızca vekil tarafından doğru gönderildiği varsayımıyla kullanılabileceğini not ediyor. Bu kalıbın sunucu içindeki kullanımı http modülü ile sunucu yazısında var. Belgelerde URL'leri desenle eşleştiren URLPattern sınıfı da bulunuyor; v23.8.0 ile geldi ve Stability: 1 - Experimental olarak işaretli.
Sık yapılan hatalar

- Taban adresin sonundaki eğik çizgiyi unutmak:
new URL('users', 'https://x.org/api/v1')sonucu/api/usersolur. - Kullanıcı girdisini taban adresle birleştirip origin kontrolü yapmamak: Mutlak ya da
//ile başlayan bir girdi tabanı tamamen yok sayar. - Sorgu dizesini elle birleştirmek:
'?q=' + metinyazmak&ve=içeren değerleri bozar;searchParams.set()kullanın. pathnamedeğerini dosya yolu sanmak: Yüzde kodları çözülmez;fileURLToPath()kullanın.- Yeni kodda
url.parse()kullanmak: DEP0169 uyarısı alırsınız ve standart dışı ayrıştırmanın güvenlik risklerini üstlenirsiniz. - Parola içeren adresi günlüğe yazmak:
url.format(u, { auth: false })ile kimlik bilgilerini çıkarın.
Sık Sorulan Sorular
URL sınıfını kullanmak için import gerekir mi?
Hayır. YAML kaydına göre URL ve URLSearchParams v10.0.0'dan beri global nesnede. Belgelerdeki örnekte import { URL } from 'node:url' ile alınan sınıfın globalThis.URL ile aynı olduğu gösteriliyor.
Bir adresin geçerli olup olmadığını nasıl kontrol ederim?
Yalnızca evet/hayır cevabı gerekiyorsa URL.canParse(adres), ayrıştırılmış nesneye de ihtiyacınız varsa URL.parse(adres) kullanın; ikincisi geçersiz girdide null döndürür. Ardından protocol alanını izin verdiğiniz şemalarla karşılaştırın.
Sorgu parametrelerini nesneye nasıl çeviririm?
Object.fromEntries(url.searchParams) tek satırda bir nesne üretir, ama tekrarlanan anahtarlarda yalnızca son değeri tutar. Aynı anahtarın bütün değerleri gerekiyorsa getAll() kullanın.
fetch ile URL nesnesini birlikte kullanabilir miyim?
Evet; adresi URL nesnesiyle kurup parametreleri searchParams ile ekledikten sonra nesneyi doğrudan isteğe verebilirsiniz. İstek gönderme ayrıntıları için fetch ile HTTP isteği yazısına bakın.
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: Node.js öğrenme rehberi · __dirname ve import.meta · path modülü · fetch ile HTTP isteği · http modülü ile sunucu · Deprecation uyarıları · JavaScript string metotları