Node.js'te klasör işlemleri node:fs/promises modülündeki birkaç fonksiyonla yapılır: mkdir(yol, { recursive: true }) iç içe klasörleri tek çağrıda oluşturur, readdir(yol) içeriği listeler, rm(yol, { recursive: true, force: true }) klasörü içindekilerle birlikte siler, cp(kaynak, hedef, { recursive: true }) kopyalar ve glob('**/*.js') desene uyan dosyaları bulur. Geçici klasörler için mkdtemp() ve await using ile kendini silen mkdtempDisposable() vardır.
Bu yazıda her fonksiyonun dönüş değerini, hangi durumda hangi hatayı verdiğini (EEXIST, ENOTEMPTY, ERR_FS_EISDIR, ENOENT), Windows ile Linux arasındaki yol ayırıcı farkını ve büyük klasörleri belleği şişirmeden gezmeyi Node.js v26.13.0 üzerinde çalıştırılmış örneklerle anlatıyoruz. Örnekler Windows'ta çalıştırıldığı için listelenen yollarda \ ayırıcısı görünür; Linux ve macOS'ta aynı kod / ile yazdırır.
mkdir: klasör oluşturmak

fsPromises.mkdir(path[, options]) bir klasör oluşturur. recursive seçeneği false iken (varsayılan) yalnızca son klasör oluşturulur; üst klasörler yoksa ENOENT, klasör zaten varsa EEXIST alırsınız. recursive: true verildiğinde eksik üst klasörler de oluşturulur ve klasör zaten varsa hata verilmez. Dönüş değeri de değişir: Promise, oluşturulan ilk klasörün yolu ile çözülür; hiçbir klasör oluşturulmadıysa undefined döner:
import { mkdir, rm } from 'node:fs/promises';
import { basename, isAbsolute } from 'node:path';
await rm('proje', { recursive: true, force: true });
const ilk = await mkdir('proje/src/lib', { recursive: true });
console.log(isAbsolute(ilk), basename(ilk));
console.log(await mkdir('proje/src/lib', { recursive: true }));
try {
await mkdir('proje/src');
} catch (err) {
console.log(err.code, err.syscall);
}
// çıktı: true proje
// çıktı: undefined
// çıktı: EEXIST mkdirİlk çağrı proje, src ve lib klasörlerini oluşturdu ve ilk oluşturulanın (proje) mutlak yolunu döndürdü. İkinci çağrıda oluşturulacak bir şey kalmadığı için sonuç undefined oldu. Üçüncü çağrı recursive olmadan var olan bir klasörü oluşturmaya çalıştığı için EEXIST verdi. Windows'ta bu örneğin döndürdüğü tam yol \\?\C:\... önekiyle geldi; bu yüzden örnekte yalnızca son parçayı yazdırıyoruz.
mode seçeneği (varsayılan 0o777) yeni klasörün izinlerini belirler; belge bu seçeneğin Windows'ta desteklenmediğini belirtir. Belgedeki bir başka Windows notu: kök dizinde ('/') recursive ile bile mkdir çağırmak EPERM hatası verir. Klasörün var olup olmadığını önceden kontrol etmeye gerek yoktur; recursive: true bu işi zaten güvenle yapar.
readdir: klasörü listelemek
readdir(path[, options]) klasördeki girdilerin adlarını bir dizi olarak döndürür; '.' ve '..' listeye girmez. Dönen adlar yalnızca addır, tam yol değildir. withFileTypes: true verirseniz string yerine fs.Dirent nesneleri alırsınız; bunların isFile(), isDirectory(), isSymbolicLink() gibi yöntemleri vardır ve her girdi için ayrıca stat çağırmanız gerekmez:
import { mkdir, writeFile, readdir } from 'node:fs/promises';
await mkdir('site/css', { recursive: true });
await writeFile('site/index.html', '<h1>Merhaba</h1>');
await writeFile('site/css/ana.css', 'body{}');
await writeFile('site/notlar.txt', 'yapılacaklar');
console.log(await readdir('site'));
for (const g of await readdir('site', { withFileTypes: true })) {
console.log(g.name, g.isDirectory() ? 'klasör' : 'dosya');
}
// çıktı: [ 'css', 'index.html', 'notlar.txt' ]
// çıktı: css klasör
// çıktı: index.html dosya
// çıktı: notlar.txt dosyaBelge Dirent türü hakkında ince bir ayrıntı verir: klasör okunurken her girdinin türü işletim sisteminin bildirdiği türdür ve dosya sistemine göre değişebilir; bazı dosya sistemleri fs.lstat()'ın döndüreceğinden farklı bir tür bildirebilir. Node.js yalnızca tür bilinmiyorsa lstat çağırır. Kesin tür gerektiğinde lstat kullanın. stat ve lstat farkı fs.stat yazımızda anlatılıyor.
Listenin sırası hakkında belgede bir garanti yoktur. Örneklerde sonucu sıralı göstermek için sort() kullanıyoruz; kendi kodunuzda da sıraya ihtiyacınız varsa açıkça sıralayın.
Alt klasörleriyle listelemek: recursive ve parentPath
v20.1.0 ve v18.17.0 ile readdir'e recursive seçeneği eklendi. Bu seçenek tüm alt klasörleri dolaşır ve göreli yollar döndürür. withFileTypes ile birlikte kullanıldığında her Dirent'in parentPath özelliği girdinin bulunduğu klasörü verir; path.join(g.parentPath, g.name) ile tam yolu kurarsınız. parentPath v24.0.0 ve v22.17.0'da kararlı (stable) olarak işaretlendi:
import { readdir } from 'node:fs/promises';
import { join } from 'node:path';
const hepsi = await readdir('site', { recursive: true });
console.log(hepsi.sort());
const dosyalar = (await readdir('site', { recursive: true, withFileTypes: true }))
.filter((g) => g.isFile())
.map((g) => join(g.parentPath, g.name))
.sort();
console.log(dosyalar);
// çıktı: [ 'css', 'css\\ana.css', 'index.html', 'notlar.txt' ]
// çıktı: [ 'site\\css\\ana.css', 'site\\index.html', 'site\\notlar.txt' ]Çıktıdaki \\, Windows'taki ters bölü ayırıcısının util.inspect tarafından kaçışlı gösterimidir; Linux'ta aynı satır 'css/ana.css' olarak görünür. Yolları elle '/' ile birleştirmek yerine path.join kullanmanız bu yüzden önemlidir; ayrıntılar path modülü yazımızda.
Büyük klasörler için opendir
readdir sonucu tek bir dizide toplar. On binlerce girdisi olan bir klasörde bu, hepsinin aynı anda bellekte olması demektir. opendir() ise bir fs.Dir nesnesi döndürür ve girdileri arabellekle parça parça okur (bufferSize varsayılanı 32). Async iterator ile gezdiğinizde döngü bitince Dir nesnesi otomatik kapatılır:
import { opendir } from 'node:fs/promises';
const dir = await opendir('site');
const adlar = [];
for await (const g of dir) adlar.push(g.name);
console.log(adlar.sort());
// çıktı: [ 'css', 'index.html', 'notlar.txt' ]glob: desene uyan dosyaları bulmak

fsPromises.glob(pattern[, options]) v22.0.0'da geldi ve v24.0.0 ile v22.17.0'da kararlı olarak işaretlendi. **/*.js gibi bir desen alır ve eşleşen yolları üreten bir async iterator döndürür. Senkron karşılığı fs.globSync() doğrudan dizi döndürür. Önemli seçenekler: cwd (varsayılan process.cwd()), exclude (fonksiyon ya da glob desenleri dizisi), withFileTypes; v26.1.0 ile gelen followSymlinks ve v26.9.0 ile gelen maxDepth:
import { glob } from 'node:fs/promises';
import { globSync } from 'node:fs';
const bulunan = [];
for await (const yol of glob('site/**/*.{html,css}')) bulunan.push(yol);
console.log(bulunan.sort());
console.log(globSync('**/*.txt', { cwd: 'site' }));
console.log(globSync('site/**/*', { exclude: ['site/css/**'] }).sort());
// çıktı: [ 'site\\css\\ana.css', 'site\\index.html' ]
// çıktı: [ 'notlar.txt' ]
// çıktı: [ 'site\\css', 'site\\index.html', 'site\\notlar.txt' ]Belgeye göre exclude dizisinde '!foo.js' gibi olumsuzlama desenleri desteklenmez. Son satırda site/css/** hariç tutulduğu için ana.css listeden çıktı ama css klasörünün kendisi kaldı. cwd verildiğinde dönen yollar o klasöre göre görelidir.
cp: klasörü kopyalamak
fsPromises.cp(src, dest[, options]) tüm klasör yapısını alt klasörler ve dosyalarla birlikte kopyalar. v22.3.0'dan beri deneysel değildir. Klasör kopyalamak için recursive: true şarttır. filter fonksiyonu her kaynak ve hedef yolu için çağrılır; false dönerse o öğe atlanır, bir klasör atlanırsa içindekilerin tamamı da atlanır. force varsayılan olarak true'dur ve var olan hedefin üzerine yazar:
import { cp, readdir } from 'node:fs/promises';
import { extname } from 'node:path';
await cp('site', 'yedek', {
recursive: true,
filter: (kaynak) => extname(kaynak) !== '.txt',
});
console.log((await readdir('yedek', { recursive: true })).sort());
// çıktı: [ 'css', 'css\\ana.css', 'index.html' ]force: false verip hedef varsa hatayı yok saymak yerine hata almak istiyorsanız errorOnExist: true ekleyin. preserveTimestamps: true kaynak dosyaların zaman damgalarını korur. Belge, klasörü klasöre kopyalarken glob desteklenmediğini ve davranışın cp dir1/ dir2/ komutuna benzediğini belirtir. Tek dosya kopyalamak için copyFile() da kullanılabilir; belge onun için atomiklik garantisi verilmediğini not eder.
Klasörü yeniden adlandırmak veya taşımak
Klasörün adını değiştirmek ya da aynı dosya sisteminde başka yere taşımak için rename(eskiYol, yeniYol) kullanılır. Learn rehberindeki klasör örnekleri de bu fonksiyonu kullanır:
import { rename, readdir, mkdir } from 'node:fs/promises';
await mkdir('eski-ad', { recursive: true });
await rename('eski-ad', 'yeni-ad');
console.log((await readdir('.')).filter((a) => a.endsWith('-ad')));
// çıktı: [ 'yeni-ad' ]Belgeye göre newPath var olan bir dosyaysa üzerine yazılır, ama orada bir klasör varsa hata verilir. Bu örnek ilk çalıştırma içindir; yeni-ad klasörü zaten varken tekrar çalıştırırsanız rename hata fırlatır.
rm ve rmdir: silmek
rmdir() yalnızca boş klasörleri siler. rm() POSIX rm komutu örnek alınarak tasarlanmıştır; dosya da klasör de silebilir ama klasör silmek için recursive: true gerekir. force: true, yol yoksa hatayı yok sayar. Hangi durumda hangi hatayı aldığınızı görmek için:
import { rm, rmdir, mkdir, writeFile } from 'node:fs/promises';
await mkdir('silinecek', { recursive: true });
await writeFile('silinecek/a.txt', 'a');
try {
await rmdir('silinecek');
} catch (err) {
console.log(err.code);
}
try {
await rm('silinecek');
} catch (err) {
console.log(err.code);
}
await rm('silinecek', { recursive: true });
try {
await rm('silinecek', { recursive: true });
} catch (err) {
console.log(err.code);
}
await rm('silinecek', { recursive: true, force: true });
console.log('bitti');
// çıktı: ENOTEMPTY
// çıktı: ERR_FS_EISDIR
// çıktı: ENOENT
// çıktı: bittiDolu klasörde rmdir sistem hatası ENOTEMPTY verdi. recursive olmadan bir klasöre rm uygulamak ise Node.js'in kendi hata kodu ERR_FS_EISDIR ("yol bir klasör") ile sonuçlandı. Klasör silindikten sonra tekrar silmeye çalışmak ENOENT verdi; force: true eklenince aynı çağrı sessizce tamamlandı. Unix'teki rm -rf davranışının karşılığı belgeye göre { recursive: true, force: true } seçenekleridir.
rm'nin maxRetries ve retryDelay seçenekleri, EBUSY, EMFILE, ENFILE, ENOTEMPTY veya EPERM hatalarında işlemi her denemede retryDelay milisaniye daha uzun bekleyerek tekrarlar. Bu seçenekler yalnızca recursive: true iken dikkate alınır; varsayılanlar 0 deneme ve 100 ms'dir. Örneğin EBUSY kaynağın o an meşgul olduğunu bildirir; böyle geçici bir durumda birkaç deneme silmenin tamamlanmasını sağlayabilir.
mkdtemp: benzersiz geçici klasör
mkdtemp(prefix) verdiğiniz önekin sonuna altı rastgele karakter ekleyerek benzersiz bir klasör oluşturur ve yolunu döndürür. Belgedeki örnek öneki path.join(os.tmpdir(), 'foo-') ile kurar; geçici dizinin yerini os.tmpdir() verir, bunu os modülü yazımızda anlatıyoruz. Aşağıdaki örnek, yalnızca çalışma klasörünün içine yazmak için göreli bir önek kullanıyor:
import { mkdtemp, rm } from 'node:fs/promises';
const klasor = await mkdtemp('gecici-');
console.log(klasor.startsWith('gecici-'), klasor.length);
await rm(klasor, { recursive: true });
// çıktı: true 13Uzunluk 13'tür: yedi karakterlik gecici- önekine altı rastgele karakter eklendi. Belge iki uyarı yapar. Birincisi, karakterler doğrudan öneke eklenir; /tmp klasörünün içinde bir klasör istiyorsanız önek platformun yol ayırıcısıyla (path.sep) bitmelidir, yoksa /tmpabc123 gibi bir kardeş klasör oluşur. İkincisi, platform farkları nedeniyle önekin sonunda X karakterleri kullanmaktan kaçının; BSD gibi bazı sistemler bunları da rastgele karakterlerle değiştirebilir.
await using ile kendini silen geçici klasör
v24.4.0 ile gelen fsPromises.mkdtempDisposable(), async olarak atılabilen (async-disposable) bir nesne döndürür. Nesnenin path özelliği klasörün yoludur; await using ile tanımlandığında blok bittiği anda klasör içindekilerle birlikte silinir. Aynı işi yapan bir remove() yöntemi de vardır. Senkron karşılığı fs.mkdtempDisposableSync(), using ile kullanılır:
import { mkdtempDisposable, writeFile, access } from 'node:fs/promises';
let yol;
{
await using tmp = await mkdtempDisposable('is-');
yol = tmp.path;
await writeFile(`${tmp.path}/ara.txt`, 'geçici veri');
console.log('içeride');
}
try {
await access(yol);
} catch (err) {
console.log(err.code);
}
// çıktı: içeride
// çıktı: ENOENTBlok bittikten sonra access çağrısı ENOENT verdi; klasör ve içindeki ara.txt otomatik silinmişti. Belgeye göre klasör silinemezse atma (dispose) işlemi hata fırlatır. Test kodlarında ya da ara dosya üreten işlerde temizliği unutma riskini ortadan kaldırır.
Büyük/küçük harf ve Unicode biçimi
Learn rehberindeki "farklı dosya sistemleri" sayfası klasör listelerken karşılaşabileceğiniz iki sürprizi açıklar. Birincisi büyük/küçük harf korunması: test/abc adıyla oluşturduğunuz klasörü bazı dosya sistemleri ABC olarak saklayabilir ve readdir size onu öyle döndürür. İkincisi Unicode biçimi: café gibi bir ad NFC biçiminde oluşturulup dosya sisteminden NFD biçiminde (farklı bayt dizisi, farklı length) dönebilir. Rehber bunların Node.js hatası olmadığını, Node.js'in adı dosya sisteminin sakladığı gibi döndürdüğünü vurgular.
Rehberin önerisi, adları kalıcı olarak değiştirmemek, yalnızca karşılaştırırken normalleştirmektir: a.normalize('NFC') === b.normalize('NFC'). Türkçe karakterli dosya adlarıyla çalışan uygulamalarda, özellikle macOS ile Linux arasında dosya taşınıyorsa, bu karşılaştırma yöntemi önemlidir. String yöntemleri için JavaScript string metotları yazımıza bakabilirsiniz.
Hangi işlem için hangi fonksiyon?

| İş | Fonksiyon | Dikkat edilecek nokta |
|---|---|---|
| İç içe klasör oluşturmak | mkdir(yol, { recursive: true }) | İlk oluşan yolu ya da undefined döndürür |
| İçeriği listelemek | readdir(yol) | Yalnızca adlar, sıra garantisi yok |
| Türüyle listelemek | readdir(yol, { withFileTypes: true }) | Dirent türü dosya sistemine bağlı olabilir |
| Tüm alt klasörler | readdir(yol, { recursive: true }) | Yol ayırıcısı platforma göre değişir |
| Desenle aramak | glob / globSync | exclude içinde olumsuzlama deseni yok |
| Klasör kopyalamak | cp(kaynak, hedef, { recursive: true }) | force varsayılan true |
| Klasör silmek | rm(yol, { recursive: true, force: true }) | rmdir yalnızca boş klasör siler |
| Geçici klasör | mkdtemp / mkdtempDisposable | Önek ayırıcıyla bitmezse kardeş klasör oluşur |
Sık yapılan hatalar
- mkdir öncesi existsSync kontrolü: gereksizdir ve yarış durumu yaratır;
recursive: trueklasör varsa sessizce geçer. - readdir sonucunu tam yol sanmak: dönen değerler yalnızca addır.
path.join(klasor, ad)ya daDirent.parentPathile tam yolu kurun. - Her girdi için ayrıca stat çağırmak: çoğu zaman
withFileTypes: trueyeterlidir; tür bilgisi listeyle birlikte gelir ve her girdi için ayrı birstatçağrısına gerek kalmaz. - rmdir ile dolu klasör silmeye çalışmak:
ENOTEMPTYalırsınız.rmilerecursive: truekullanın. - Eski rmdir recursive seçeneğine güvenmek: bu seçenek v25.0.0'da kaldırıldı ve artık etkisi yok.
- Yolları '/' ile elle birleştirmek: platformlar arası taşınabilirlik için
path.joinvepath.sepkullanın. - mkdtemp önekinde ayırıcıyı unutmak:
mkdtemp('/tmp')/tmpiçinde değil yanında bir klasör oluşturur.
Sık Sorulan Sorular
Klasör yoksa oluşturmanın en kısa yolu nedir?
await mkdir(yol, { recursive: true }). Klasör zaten varsa hata vermez, üst klasörler eksikse onları da oluşturur. Önceden varlık kontrolü yapmanıza gerek yoktur.
Bir klasördeki yalnızca dosyaları nasıl listelerim?
readdir(yol, { withFileTypes: true }) ile Dirent nesnelerini alın ve filter((g) => g.isFile()) uygulayın. Alt klasörler dahil istiyorsanız recursive: true ekleyin ve tam yol için parentPath kullanın.
Dolu bir klasörü nasıl silerim?
await rm(yol, { recursive: true, force: true }). force klasör yoksa hatayı yok sayar. EBUSY veya EPERM gibi hatalar ara sıra silmeyi yarıda bırakıyorsa maxRetries seçeneğini deneyin.
glob fonksiyonu kararlı mı?
Evet. Belgedeki değişiklik kaydına göre fs.glob, fs.globSync ve fsPromises.glob v24.0.0 ve v22.17.0'da kararlı olarak işaretlendi. maxDepth seçeneği v26.9.0, followSymlinks seçeneği v26.1.0 ile eklendi; bu seçenekleri eski sürümlerde kullanamazsınız.
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 · Dosya yazma · fs.stat ve varlık kontrolü · path modülü · fs.watch ile izleme · Node.js nedir