node:path modülü dosya ve klasör yollarını metin olarak işler: path.join() parçaları platformun ayırıcısıyla birleştirip sonucu normalleştirir, path.resolve() parçaları sağdan sola işleyerek mutlak bir yol üretir, path.basename(), path.dirname() ve path.extname() bir yolun dosya adını, klasörünü ve uzantısını verir. Modülün davranışı işletim sistemine göre değişir: Windows'ta Windows yollarını, Linux ve macOS'ta POSIX yollarını varsayar. Her sistemde aynı sonucu almak için path.posix veya path.win32 kullanırsınız.
Bu yazıda modülün bütün fonksiyonlarını, uç durumlarıyla birlikte ele alıyoruz: join ile resolve farkı, extname fonksiyonunun noktayla başlayan dosyalardaki davranışı, parse/format, relative, isAbsolute, glob eşleştirme için matchesGlob ve kullanıcıdan gelen yollarda klasör dışına çıkışı engelleyen bir denetim fonksiyonu. Çıktıların her sistemde aynı olması için örneklerin çoğunda path.posix kullandık; Windows davranışını path.win32 ile ayrıca gösterdik. Örnekler Windows üzerinde Node.js v26.13.0 ile çalıştırıldı.
Windows ve POSIX: path modülü neden işletim sistemine göre değişir?

Linux ve macOS'ta yollar /home/ayse/notlar.txt biçimindedir ve ayırıcı / karakteridir. Windows'ta ise C:\Users\ayse\notlar.txt gibi sürücü harfi ve ters eğik çizgi kullanılır. Belgelere göre node:path modülünün varsayılan davranışı uygulamanın çalıştığı işletim sistemine göre belirlenir; Windows'ta varsayılan nesne aslında path.win32 uygulamasıdır. Aynı Windows yolunu iki uygulamaya verdiğimizde fark açıkça görülüyor:
// p3.mjs (Windows'ta çalıştırıldı)
import path from 'node:path';
console.log(path.sep, path.delimiter, path === path.win32);
console.log(path.posix.basename('C:\\temp\\fatura.html'));
console.log(path.win32.basename('C:\\temp\\fatura.html'));
console.log(path.win32.join('C:\\proje', 'src', 'app.js'));
console.log(path.win32.normalize('C:/proje//src/../dist'));
console.log(path.posix.relative('/srv/app/src', '/srv/app/public/css'));
console.log(path.win32.relative('C:\\proje\\src', 'C:\\proje\\dist'));
console.log(path.win32.basename('C:\\FOTO.JPG', '.jpg'));
console.log(path.posix.isAbsolute('/a/../..'), path.posix.isAbsolute('./a'), path.win32.isAbsolute('C:/x'), path.win32.isAbsolute('C:x'));
// çıktı: \ ; true
// çıktı: C:\temp\fatura.html
// çıktı: fatura.html
// çıktı: C:\proje\src\app.js
// çıktı: C:\proje\dist
// çıktı: ../public/css
// çıktı: ..\dist
// çıktı: FOTO.JPG
// çıktı: true false true falsePOSIX uygulaması ters eğik çizgiyi sıradan bir karakter saydığı için bütün metni dosya adı olarak döndürdü. Linux sunucuda çalışan kodunuz Windows biçiminde yollar işliyorsa (örneğin bir Windows istemcisinden yüklenen dosya adlarını) path.win32 kullanmanız gerekir; tersi de geçerli. Belgelere göre bu nesnelere require('node:path/posix') ve require('node:path/win32') ile de erişebilirsiniz. Windows'ta hem / hem \ ayırıcı olarak kabul edilir ama path fonksiyonları çıktıya yalnızca ters eğik çizgi yazar.
Örnekteki son iki ayrıntı da belgelerden geliyor. basename uzantı karşılaştırmasında büyük/küçük harfe duyarlıdır; Windows FOTO.JPG ile foto.jpg dosyalarını aynı sayar ama fonksiyon .jpg son ekini kaldırmadı. C:x gibi ters eğik çizgisiz bir sürücü yolu ise mutlak değildir; belgeler Windows'un her sürücü için ayrı bir çalışma klasörü tuttuğunu, bu yüzden path.resolve('C:\\') ile path.resolve('C:') sonuçlarının farklı olabileceğini belirtiyor.
path.join ile path.resolve arasındaki fark
İkisi de yol parçalarını birleştirir, ama mantıkları farklıdır. path.join() verilen bütün parçaları ayırıcıyla uç uca ekler ve sonucu normalleştirir; boş parçaları yok sayar ve sonuç boş kalırsa çalışma klasörünü temsil eden '.' döndürür. path.resolve() ise parçaları sağdan sola işler ve mutlak bir yol oluşana kadar her parçayı başa ekler. Mutlak bir parçaya rastladığında ondan öncekileri tamamen bırakır; bütün parçalar işlendiği halde mutlak bir yol oluşmadıysa başa geçerli çalışma klasörünü koyar. Sonuç normalleştirilir ve kök dışında sondaki eğik çizgi kaldırılır.
// p1.mjs
import path from 'node:path';
const p = path.posix;
console.log(p.join('/srv', 'app', '../veri', 'rapor.csv'));
console.log(p.join('uploads', '', 'resim.png'));
console.log(p.join(''));
console.log(p.resolve('/srv/app', 'public', 'index.html'));
console.log(p.resolve('/srv/app', '/etc', 'hosts'));
console.log(p.join('/srv/app', '/etc', 'hosts'));
console.log(p.normalize('/srv//app/./logs/../tmp/'));
// çıktı: /srv/veri/rapor.csv
// çıktı: uploads/resim.png
// çıktı: .
// çıktı: /srv/app/public/index.html
// çıktı: /etc/hosts
// çıktı: /srv/app/etc/hosts
// çıktı: /srv/app/tmp/Beşinci ve altıncı satırlar farkı en net gösteren örnek: '/etc' mutlak bir parça olduğu için resolve /srv/app kısmını attı ve /etc/hosts üretti; join ise onu sıradan bir parça gibi ekleyip /srv/app/etc/hosts verdi. Kullanıcıdan gelen bir yolu resolve ile kök klasörünüze eklerken bu davranış önemli bir güvenlik ayrıntısıdır; aşağıda ayrıca ele alıyoruz. normalize ise .. ve . parçalarını çözer, art arda gelen ayırıcıları teke indirir ve sondaki ayırıcıyı korur.
basename, dirname, extname: yolu parçalara ayırmak
path.basename(yol, sonEk) yolun son parçasını döndürür ve isteğe bağlı ikinci argümanla verilen son eki kaldırır. path.dirname() son parçadan önceki kısmı verir. İkisi de sondaki ayırıcıyı yok sayar. path.extname() son parçadaki son noktadan itibaren uzantıyı verir; ama belgelerdeki kurala göre son parçada hiç nokta yoksa ya da tek nokta dosya adının ilk karakteriyse boş dize döndürür. Bu yüzden .env gibi gizli dosyaların uzantısı yoktur:
// p2.mjs
import path from 'node:path';
const p = path.posix;
const dosya = '/home/ayse/belgeler/rapor.2026.pdf';
console.log(p.basename(dosya));
console.log(p.basename(dosya, '.pdf'));
console.log(p.basename(dosya, p.extname(dosya)));
console.log(p.dirname(dosya));
console.log(p.extname(dosya));
console.log([p.extname('.env'), p.extname('arsiv.tar.gz'), p.extname('README'), p.extname('nokta.')]);
console.log(p.parse(dosya));
console.log(p.format({ dir: '/tmp', name: 'yedek', ext: 'zip' }));
// çıktı: rapor.2026.pdf
// çıktı: rapor.2026
// çıktı: rapor.2026
// çıktı: /home/ayse/belgeler
// çıktı: .pdf
// çıktı: [ '', '.gz', '', '.' ]
// çıktı: {
// çıktı: root: '/',
// çıktı: dir: '/home/ayse/belgeler',
// çıktı: base: 'rapor.2026.pdf',
// çıktı: ext: '.pdf',
// çıktı: name: 'rapor.2026'
// çıktı: }
// çıktı: /tmp/yedek.ziparsiv.tar.gz için yalnızca .gz döndü; çok parçalı uzantıları (.tar.gz) ayırt etmek isterseniz bunu kendiniz yapmanız gerekir. path.parse() bir yolu root, dir, base, name ve ext alanlarına böler; path.format() bunun tersini yapar. format için iki öncelik kuralı var: dir verilmişse root yok sayılır, base verilmişse name ve ext yok sayılır. YAML kaydına göre v19.0.0'dan beri ext değerinde nokta yoksa otomatik ekleniyor; örnekteki 'zip' bu yüzden .zip oldu.
Bir dosyanın adını koruyup uzantısını değiştirmek bu iki fonksiyonun en yaygın birlikte kullanımıdır: path.format({ ...path.parse(yol), base: undefined, ext: '.webp' }). base alanını boşaltmak gerekir, çünkü dolu olduğu sürece name ve ext dikkate alınmaz. Aşağıdaki örnekte ikinci satır tam olarak bu hatayı gösteriyor: ext değiştiği halde sonuç eski uzantıyla geldi. Aynı örnekte farklı sürücülerdeki iki Windows yolu için relative fonksiyonunun göreli yol üretemeyip hedefin kendisini döndürdüğünü ve toNamespacedPath davranışını da görüyorsunuz:
// p6.mjs
import path from 'node:path';
const p = path.posix;
const resim = '/srv/app/uploads/kedi.png';
console.log(p.format({ ...p.parse(resim), base: undefined, ext: '.webp' }));
console.log(p.format({ ...p.parse(resim), ext: '.webp' }));
console.log(p.join('./src', 'app.js'));
console.log(path.win32.relative('C:\\proje', 'D:\\yedek'));
console.log(path.win32.toNamespacedPath('C:\\proje\\app.js'));
console.log(path.posix.toNamespacedPath('/srv/app.js'));
console.log('/usr/local/bin:/usr/bin:/bin'.split(path.posix.delimiter));
// çıktı: /srv/app/uploads/kedi.webp
// çıktı: /srv/app/uploads/kedi.png
// çıktı: src/app.js
// çıktı: D:\yedek
// çıktı: \\?\C:\proje\app.js
// çıktı: /srv/app.js
// çıktı: [ '/usr/local/bin', '/usr/bin', '/bin' ]Görsel işleyen bir servis yazıyorsanız bu kalıp, yüklenen dosyanın dönüştürülmüş kopyasını aynı klasöre yeni uzantıyla kaydetmenin en kısa yoludur. Dosya adını kullanıcı verdiyse önce aşağıdaki güvenli yol denetiminden geçirin.
relative, isAbsolute, sep ve delimiter

path.relative(from, to), from klasöründen to konumuna giden göreli yolu verir; ikisi aynı yere çözümlenirse boş dize döner. path.isAbsolute() yolun mutlak olup olmadığını söyler, ama belgeler önemli bir uyarı ekliyor: fonksiyon yalnızca metne bakar, bu yüzden yol geçişi (path traversal) saldırılarını önlemek için güvenli değildir; /baz/../.. da mutlak sayılır. path.sep parça ayırıcısıdır (POSIX'te /, Windows'ta \); path.delimiter ise PATH gibi ortam değişkenlerinde yolları ayıran karakterdir (POSIX'te :, Windows'ta ;). process.env.PATH.split(path.delimiter) her iki sistemde de doğru çalışır.
| Fonksiyon | Ne yapar | Dikkat |
|---|---|---|
| join(...parçalar) | Parçaları ayırıcıyla birleştirir, normalleştirir | Mutlak parçayı sıradan parça gibi ekler |
| resolve(...parçalar) | Sağdan sola mutlak yol üretir | Mutlak parça öncekileri siler; gerekirse cwd ekler |
| normalize(yol) | . ve .. parçalarını çözer | Sondaki ayırıcıyı korur |
| basename(yol, sonEk) | Son parça | Son ek büyük/küçük harfe duyarlı |
| dirname(yol) | Son parçadan önceki kısım | Sondaki ayırıcıyı yok sayar |
| extname(yol) | Son nokta ve sonrası | .env için boş dize |
| parse / format | Nesneye ayırma ve geri birleştirme | base, name ve ext alanlarını ezer |
| relative(from, to) | Göreli yol | Aynı yer için boş dize |
| isAbsolute(yol) | Mutlak mı | Güvenlik denetimi için yetmez |
| matchesGlob(yol, desen) | Glob eşleşmesi | v24.8.0 ile kararlı |
Kullanıcıdan gelen yolları güvenle birleştirmek
Bir dosya yükleme ya da indirme uç noktasında kullanıcının verdiği dosya adını kök klasörünüze eklemek sık yapılan bir iştir ve path modülü bu işte hem yardımcı hem de tuzak olabilir. path.join(KOK, '../../etc/passwd') sonucu normalleştirildiği için kök klasörün dışına çıkar. path.resolve(KOK, '/etc/shadow') ise mutlak parça yüzünden kökü tamamen atar. Güvenilir bir denetim için önce yolu resolve ile mutlak hale getirip sonra relative ile köke göre konumuna bakabilirsiniz: sonuç .. ile başlıyorsa ya da mutlaksa hedef kökün dışındadır.
// p4.mjs
import path from 'node:path';
const p = path.posix;
const KOK = '/srv/app/uploads';
function guvenliYol(istek) {
const hedef = p.resolve(KOK, istek);
const goreli = p.relative(KOK, hedef);
if (goreli === '' || goreli.startsWith('..') || p.isAbsolute(goreli)) {
throw new Error('Kök dışına çıkış engellendi: ' + istek);
}
return hedef;
}
console.log(guvenliYol('avatar/ali.png'));
for (const kotu of ['../../etc/passwd', '/etc/shadow', '.']) {
try {
guvenliYol(kotu);
} catch (err) {
console.log(err.message);
}
}
console.log(p.join(KOK, '../../etc/passwd'));
// çıktı: /srv/app/uploads/avatar/ali.png
// çıktı: Kök dışına çıkış engellendi: ../../etc/passwd
// çıktı: Kök dışına çıkış engellendi: /etc/shadow
// çıktı: Kök dışına çıkış engellendi: .
// çıktı: /srv/etc/passwdSon satır, denetimsiz join kullanımının nereye varabileceğini gösteriyor. Bu fonksiyon yalnızca metin düzeyinde bir denetimdir; sembolik bağlantılar kök içinden dışarıyı gösteriyorsa onları yakalamaz. Daha sıkı bir korumaya ihtiyacınız varsa gerçek yolu fs.realpath() ile çözüp aynı denetimi onun üzerinde tekrarlayın. Windows'ta aynı mantığı path.win32 ile kurarken sürücü harflerinin farklı olabileceğini de hesaba katın: farklı sürücüdeki bir hedef için relative mutlak bir yol döndürür ve yukarıdaki isAbsolute kontrolü bunu yakalar. Node'un ayrıca bir izin modeli de var; dosya sistemi erişimini süreç düzeyinde kısıtlamak için permission model yazısına bakabilirsiniz.
path.matchesGlob: bir yol desene uyuyor mu?
path.matchesGlob(yol, desen), bir yolun glob desenine uyup uymadığını true veya false olarak döndürür. YAML kaydına göre v22.5.0 ve v20.17.0 ile geldi, v24.8.0 ve v22.20.0 ile kararlı (stable) olarak işaretlendi. Tek yıldız bir klasör sınırını geçmez; alt klasörleri de kapsamak için ** kullanılır:
// p5.mjs
import path from 'node:path';
const p = path.posix;
console.log(p.matchesGlob('src/app.js', 'src/*.js'));
console.log(p.matchesGlob('src/lib/db.js', 'src/*.js'));
console.log(p.matchesGlob('src/lib/db.js', 'src/**/*.js'));
console.log(p.matchesGlob('notlar.md', '*.{md,txt}'));
try {
path.join('a', 42);
} catch (err) {
console.log(err.name, err.code);
console.log(err.message);
}
// çıktı: true
// çıktı: false
// çıktı: true
// çıktı: true
// çıktı: TypeError ERR_INVALID_ARG_TYPE
// çıktı: The "path" argument must be of type string. Received type number (42)Örneğin sonundaki hata da belgelerin her fonksiyon için söylediği kuralı gösteriyor: dize olmayan bir argüman TypeError fırlatır. Yolu bir sayıdan, undefined değerinden ya da bir Buffer nesnesinden üretiyorsanız önce dizeye çevirin. Bir klasördeki dosyaları desene göre listelemek isterseniz fs.glob() fonksiyonu daha uygundur; bu konu klasör işlemleri yazısında anlatılıyor.
Sık yapılan hatalar

- Yolları dize birleştirmeyle kurmak:
dir + '/' + dosyaWindows'ta karışık ayırıcılar ve çift eğik çizgiler üretir;path.join()kullanın. joinileresolvesonucunu aynı sanmak: Mutlak bir parçaresolvesonucunda öncekileri siler.resolvesonucunun dosya klasörüne göre olduğunu sanmak: Mutlak yol oluşmazsa çalışma klasörü eklenir; kod dosyasına göre yol için import.meta.dirname kullanın.isAbsoluteile güvenlik denetimi yapmak: Belgeler bunun yol geçişini önlemek için güvenli olmadığını söylüyor.extnameile dosya türüne karar vermek: Uzantı kullanıcının yazdığı metindir; içerik türünü kanıtlamaz.- URL'leri
pathile işlemek:https://adreslerinipath.joinile birleştirmek çift eğik çizgiyi bozar; URL sınıfını kullanın.
Sık Sorulan Sorular
Windows ve Linux için ayrı kod yazmam gerekiyor mu?
Hayır. Varsayılan path nesnesi çalıştığı sistemin kurallarını uygular; join, resolve ve dirname kullandığınız sürece kodunuz iki sistemde de doğru yol üretir. Yalnızca diğer sistemin yollarını işlemeniz gerektiğinde path.posix veya path.win32 kullanırsınız.
path.join sonucunda neden başta ./ yok?
join sonucu normalleştirdiği için gereksiz ./ parçalarını kaldırır: path.join('./src', 'app.js') sonucu src/app.js olur. Bu değer fs fonksiyonlarında yine çalışma klasörüne göre göreli yol olarak çalışır.
Uzantısız dosya adını nasıl alırım?
Learn rehberindeki kalıp: path.basename(yol, path.extname(yol)). Alternatif olarak path.parse(yol).name aynı sonucu verir.
path.resolve() argümansız çağrılırsa ne döndürür?
Belgelere göre hiçbir parça verilmezse path.resolve() geçerli çalışma klasörünün mutlak yolunu döndürür; yani process.cwd() ile aynı bilgiyi verir. Boş dize parçaları da yok sayıldığı için path.resolve('') aynı sonucu üretir. Bu yüzden resolve çıktısı, programı hangi klasörden başlattığınıza bağlıdır; aynı betiği bir servis yöneticisi farklı bir klasörden başlatırsa farklı yollar üretebilir. Kod dosyasına bağlı sabit bir yol istiyorsanız başlangıç noktası olarak import.meta.dirname (CommonJS'te __dirname) verin: path.resolve(import.meta.dirname, 'public').
path.toNamespacedPath ne işe yarar?
Yalnızca Windows'ta anlamlıdır: bir yolun \\?\ önekli ad alanı (namespace) karşılığını döndürür. POSIX sistemlerde belgelere göre hiçbir şey yapmaz ve yolu değiştirmeden geri verir.
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 · URL ve URLSearchParams · Dosya var mı kontrolü · Klasör işlemleri · os modülü · Node.js nedir?