Node.js'te bir dosyayı okumanın en kısa yolu node:fs/promises modülündeki readFile() fonksiyonudur: await readFile('notlar.txt', 'utf8') dosyanın tüm içeriğini bir string olarak döndürür. Kodlama (encoding) vermezseniz sonuç string değil, ham baytları tutan bir Buffer olur. Aynı işin callback alan fs.readFile() ve olay döngüsünü bekleten fs.readFileSync() biçimleri de vardır; üçü de dosyanın tamamını belleğe alır.

Bu yazıda üç biçimin nasıl kullanıldığını, kodlama seçimini, dosya yolunun nereye göre çözüldüğünü, ENOENT ve EISDIR gibi hataları, büyük dosyalarda neden stream'e geçmeniz gerektiğini, satır satır okumayı, okumayı iptal etmeyi ve v26.4.0 ile gelen buffer seçeneğini Node.js v26.13.0 üzerinde çalıştırılmış örneklerle anlatıyoruz. Dosyaya yazmak için writeFile ve appendFile yazımıza, üç API biçiminin ayrıntılı karşılaştırması için fs/promises, callback ve sync farkı yazımıza bakabilirsiniz.

readFile için üç biçim

readFile için üç biçim

node:fs modülündeki her dosya sistemi işleminin senkron, callback tabanlı ve Promise tabanlı bir biçimi bulunur. Okuma için bunlar fs.readFileSync(), fs.readFile() ve fsPromises.readFile() fonksiyonlarıdır. Promise biçimini import { readFile } from 'node:fs/promises' ile, diğer ikisini import { readFile, readFileSync } from 'node:fs' ile alırsınız. CommonJS kullanan projelerde aynı modüller require('node:fs/promises') ve require('node:fs') ile yüklenir.

Üç biçimin imzası neredeyse aynıdır: ilk parametre dosya yolu, ikinci parametre seçenekler nesnesi ya da doğrudan kodlama adıdır. Seçenekler arasında encoding (varsayılan null), flag (varsayılan 'r'), signal ve buffer bulunur. Senkron biçimde signal yoktur; iptal edilecek bir arka plan işi olmadığı için bu mantıklıdır. Fark, sonucun size nasıl ulaştığındadır: Promise biçimi bir Promise döndürür, callback biçimi sonucu (err, data) parametreleriyle bir fonksiyona verir, senkron biçim değeri doğrudan döndürür ve hatayı fırlatır.

Kodlama: Buffer mı, string mi?

Belgelere göre encoding belirtilmezse veri bir Buffer nesnesi olarak gelir; belirtilirse string olur. Metin dosyası okurken en sık yapılan hata kodlamayı unutup ekrana <Buffer ...> basılmasıdır. Aşağıdaki örnekte aynı dosya önce kodlamasız, sonra 'utf8' ile okunuyor:

javascript
import { readFile } from 'node:fs/promises';

const ham = await readFile('notlar.txt');
console.log(ham);
console.log(ham.length);

const metin = await readFile('notlar.txt', 'utf8');
console.log(metin.length);
console.log(metin.split('\n')[1]);
// çıktı: <Buffer 4d 65 72 68 61 62 61 20 4e 6f 64 65 2e 6a 73 0a c4 b0 6b 69 6e 63 69 20 73 61 74 c4 b1 72 3a 20 c4 9f c3 bc c5 9f c4 b1 c3 b6 c3 a7 0a c3 9c c3 a7 c3 ... 13 more bytes>
// çıktı: 63
// çıktı: 50
// çıktı: İkinci satır: ğüşıöç

Dikkat edin: Buffer uzunluğu 63, string uzunluğu 50. Aradaki fark Türkçe karakterlerden gelir; İ, ğ, ş gibi harfler UTF-8'de iki bayt tutar ama string'de tek karakter sayılır. Bu yüzden dosya boyutu (bayt) ile metin uzunluğunu (karakter) karıştırmamak gerekir. İkili dosyalarda (resim, PDF, sıkıştırılmış arşiv) kodlama vermez, Buffer ile çalışırsınız. Buffer'ın ayrıntıları için Buffer kullanımı yazımıza göz atabilirsiniz.

İkinci parametreyi string olarak vermek ('utf8') ile nesne olarak vermek ({ encoding: 'utf8' }) aynı sonucu verir. Belge, seçenekler bir string ise bunun kodlamayı belirttiğini açıkça söyler. Nesne biçimi, aynı çağrıda signal ya da flag da vermeniz gerektiğinde işe yarar.

Callback ve senkron biçim

Callback biçiminde fonksiyonun son parametresi bir tamamlanma fonksiyonudur. Node.js'in callback kuralı gereği ilk parametre her zaman hata için ayrılmıştır; işlem başarılıysa bu değer null ya da undefined olur. Senkron biçim ise hatayı doğrudan fırlattığı için try...catch ile kullanılır:

javascript
import { readFile, readFileSync } from 'node:fs';

readFile('notlar.txt', 'utf8', (err, data) => {
  if (err) {
    console.error('okunamadı:', err.message);
    return;
  }
  console.log('callback:', data.split('\n')[0]);
});

const s = readFileSync('notlar.txt', { encoding: 'utf8' });
console.log('sync:', s.split('\n')[0]);
// çıktı: sync: Merhaba Node.js
// çıktı: callback: Merhaba Node.js

Kodda callback çağrısı yukarıda olduğu halde önce sync: satırı basılır. Çünkü callback biçimi işi arka plana bırakır ve kodun devam etmesine izin verir; senkron çağrı ise dosya okunana kadar olay döngüsünü ve sonraki JavaScript kodunu durdurur. Bu davranışın arkasındaki olay döngüsü için nextTick ve setImmediate yazımıza, callback deseninin dil tarafı için JavaScript callback yazımıza bakabilirsiniz.

Senkron okuma her zaman kötü değildir. Uygulama açılırken bir kez okunan küçük bir ayar dosyası ya da tek seferlik bir komut satırı betiği için readFileSync kodu sadeleştirir. Sorun, bir HTTP sunucusunun her isteğinde senkron okuma yapmaktır: okuma süresince sunucu başka hiçbir isteğe cevap veremez. Olay döngüsünü bloklamanın etkileri Node.js performans optimizasyonu yazımızda ele alınıyor.

Dosya yolu nereye göre çözülür?

String olarak verdiğiniz göreli yollar, betiğin bulunduğu klasöre göre değil, process.cwd() ile dönen çalışma dizinine göre çözülür. Yani node betikler/oku.mjs komutunu proje kökünden çalıştırırsanız readFile('ayar.json') kökteki ayar.json dosyasını arar. Betiğin yanındaki dosyayı okumak için belgelerdeki örnekte olduğu gibi new URL('./ayar.json', import.meta.url) kullanabilirsiniz; fs fonksiyonlarının çoğu file: protokollü bir URL nesnesini yol olarak kabul eder:

javascript
import { readFile } from 'node:fs/promises';

const yol = new URL('./ayar.json', import.meta.url);
const ayar = JSON.parse(await readFile(yol, 'utf8'));
console.log(ayar);
console.log(ayar.port + 1);
// çıktı: { port: 3000, debug: true }
// çıktı: 3001

readFile JSON'u kendisi ayrıştırmaz; size metni verir, JSON.parse ile nesneye siz çevirirsiniz. Ayrıştırmanın kuralları ve hata durumları için JSON.parse ve stringify yazımıza bakabilirsiniz. ESM'de __dirname olmadığı için import.meta.dirname ve import.meta.filename seçeneklerini __dirname ve import.meta yazımızda, yolları birleştirmeyi path modülü yazımızda, file: URL'lerini ise URL ve URLSearchParams yazımızda anlatıyoruz.

Hatalar: ENOENT, EISDIR ve diğerleri

Hatalar: ENOENT, EISDIR ve diğerleri

Okuma başarısız olduğunda gelen hata bir sistem hatasıdır. Nesnenin code alanı hatanın türünü, syscall alanı başarısız olan sistem çağrısını taşır. Kodunuzda mesaj metnine değil code değerine bakmalısınız; mesaj insanlara yöneliktir. Aşağıdaki örnekte olmayan bir dosya ve bir klasör okunmaya çalışılıyor (çıktılardaki tam yollar kısaltılmıştır):

javascript
import { readFile } from 'node:fs/promises';

try {
  await readFile('yok.txt', 'utf8');
} catch (err) {
  console.log(err.name);
  console.log(err.code);
  console.log(err.syscall);
  console.log(err.message);
}

try {
  await readFile('klasor');
} catch (err) {
  console.log(err.code, err.message);
}
// çıktı: Error
// çıktı: ENOENT
// çıktı: open
// çıktı: ENOENT: no such file or directory, open '/home/kullanici/proje/yok.txt'
// çıktı: EISDIR EISDIR: illegal operation on a directory, read '/home/kullanici/proje/klasor'

ENOENT yoldaki bir parçanın bulunmadığını söyler; çoğu zaman yanlış çalışma dizini ya da yazım hatası anlamına gelir. Belgeye göre yol bir klasörse davranış platforma bağlıdır: macOS, Linux ve Windows'ta hata döner, FreeBSD'de ise klasör içeriğinin bir temsili döndürülür. Sık görülen diğer kodlar EACCES (izin yok) ve EMFILE (aynı anda çok fazla açık dosya) olur. Bu kodların listesi ve anlamları için ENOENT, EADDRINUSE ve sistem hataları yazımıza bakabilirsiniz.

Büyük dosyalar: readFile neden yetmez?

Learn rehberi açıkça belirtir: fs.readFile(), fs.readFileSync() ve fsPromises.readFile() verinin tamamını belleğe aldıktan sonra size döner. Birkaç kilobaytlık ayar dosyasında bu sorun değildir; yüzlerce megabaytlık bir log dosyasında ise bellek kullanımı ve süre ciddi biçimde artar. Hata belgesi ayrıca fs.readFile() için 2 GiB üst sınır olduğunu, bu sınırı aşan okumada ERR_FS_FILE_TOO_LARGE hatası alındığını ve bunun Buffer değil iç bir G/Ç kısıtı olduğunu söyler.

API belgesindeki performans notuna göre fs.readFile() dosyayı parça parça okur ve her parça arasında olay döngüsüne fırsat tanır. Normal dosyalarda her okuma 512 KiB işler; boyutu belirlenemeyen dosyalarda (örneğin bir pipe) her okuma 64 KiB yükler. Bu, aynı libuv iş parçacığı havuzunu kullanan diğer işlere daha az etki eder ama tüm dosyanın okunması daha uzun sürer. En hızlı okuma gereken uygulamalar için belge fs.read() ile okumayı uygulamanın kendisinin yönetmesini önerir.

Büyük dosyalar için önerilen yol fs.createReadStream() ile stream kullanmaktır. Stream dosyayı küçük parçalar (chunk) halinde verir; bellekte aynı anda yalnızca birkaç parça bulunur. Varsayılan highWaterMark değeri 64 * 1024 bayttır. Aşağıdaki örnekte etkisini görmek için bu değeri 16 bayta indiriyoruz ve start/end ile dosyanın yalnızca ilk yedi baytını okuyoruz:

javascript
import { createReadStream } from 'node:fs';

const akis = createReadStream('notlar.txt', { encoding: 'utf8', highWaterMark: 16 });
let parca = 0;
for await (const chunk of akis) {
  parca++;
}
console.log('parça sayısı:', parca);

const aralik = createReadStream('notlar.txt', { start: 0, end: 6, encoding: 'utf8' });
for await (const chunk of aralik) console.log(JSON.stringify(chunk));
// çıktı: parça sayısı: 4
// çıktı: "Merhaba"

63 baytlık dosya 16 baytlık parçalarla dört okumada bitti. start ve end değerleri her ikisi de dahil olacak şekilde 0'dan sayılır; bu yüzden 0-6 aralığı yedi bayt verir. Stream'i for await...of ile tüketmek en okunaklı yoldur; stream'in olayları, türleri ve geri basınç (backpressure) konusu stream nedir yazımızda ve pipeline ve backpressure yazımızda anlatılıyor.

Satır satır okuma: filehandle.readLines()

CSV ya da log dosyalarını satır satır işlemek çok sık ihtiyaçtır. fsPromises.open() ile açtığınız bir FileHandle üzerinde readLines() çağırırsanız Node.js sizin için bir readline arayüzü kurar ve dosyayı stream olarak okur. Satır sonları otomatik ayrılır, bellekte dosyanın tamamı tutulmaz:

javascript
import { open } from 'node:fs/promises';

const dosya = await open('notlar.txt');
let no = 0;
for await (const satir of dosya.readLines()) {
  no++;
  console.log(no, satir);
}
// çıktı: 1 Merhaba Node.js
// çıktı: 2 İkinci satır: ğüşıöç
// çıktı: 3 Üçüncü satır

readLines() seçenekleri createReadStream() ile aynıdır ve autoClose varsayılan olarak true olduğu için okuma bitince dosya kapanır. Kullanıcıdan terminalde girdi almak ya da readline modülünü doğrudan kullanmak için readline ile kullanıcı girdisi yazımıza bakın.

Okumayı iptal etmek: AbortSignal

readFile seçeneklerine bir AbortSignal verebilirsiniz. Sinyal tetiklendiğinde Promise AbortError ile reddedilir. Belgenin önemli bir notu var: iptal, işletim sistemine gitmiş tek tek okuma isteklerini durdurmaz; yalnızca readFile'ın kendi içinde yaptığı arabelleğe almayı keser.

javascript
import { readFile } from 'node:fs/promises';

const ac = new AbortController();
const soz = readFile('notlar.txt', { signal: ac.signal });
ac.abort();
try {
  await soz;
} catch (err) {
  console.log(err.name, err.code);
}
// çıktı: AbortError ABORT_ERR

Zaman aşımı için AbortSignal.timeout(ms), birden fazla sinyali birleştirmek için AbortSignal.any() kullanılabilir. Bu araçları fs, timers ve fetch ile birlikte AbortController ile iptal ve zaman aşımı yazımızda anlatıyoruz.

v26.4.0 ile gelen buffer seçeneği

Belgelerdeki YAML kaydına göre fsPromises.readFile(), fs.readFile(), fs.readFileSync() ve filehandle.readFile() fonksiyonlarına v26.4.0 ile buffer seçeneği eklendi. Bu seçenekle okumanın yapılacağı tamponu siz verirsiniz ya da dosya boyutunu alıp tampon döndüren bir fonksiyon geçersiniz. Kodlama verilmezse dönen Buffer, verdiğiniz tampon üzerinde yalnızca okunan baytları gösteren bir görünümdür. Tampon dosyaya yetmezse işlem hata ile biter:

javascript
import { Buffer } from 'node:buffer';
import { readFile } from 'node:fs/promises';

const tampon = Buffer.alloc(1024);
const icerik = await readFile('notlar.txt', { buffer: tampon });
console.log(icerik.length, icerik.buffer === tampon.buffer);

try {
  await readFile('notlar.txt', { buffer: Buffer.alloc(4) });
} catch (err) {
  console.log(err.name, err.code);
  console.log(err.message);
}
// çıktı: 63 true
// çıktı: TypeError ERR_INVALID_ARG_VALUE
// çıktı: The property 'options.buffer.byteLength' is smaller than the file size of 63 bytes. Received 4

Bu seçenek, çok sayıda küçük dosyayı art arda okurken her seferinde yeni bellek ayırmak istemediğiniz durumlar için düşünülmüştür. Eski sürümler bu seçeneği tanımaz; projeniz v26.4.0 altındaki bir sürümde çalışacaksa kullanmayın.

Kodlama ve satır sonu tuzakları

Dosyayı yanlış kodlamayla okursanız hata almazsınız; Node.js baytları istediğiniz kodlamaya göre yorumlar ve bozuk görünen bir metin üretir. Aşağıda UTF-8 olarak kaydedilmiş Türkçe metin 'latin1' ile okunuyor:

javascript
import { readFile } from 'node:fs/promises';

const yanlis = await readFile('notlar.txt', 'latin1');
console.log(yanlis.split('\n')[1].slice(0, 7));
console.log(yanlis.includes('ş'));
const dogru = await readFile('notlar.txt', 'utf8');
console.log(dogru.split('\n')[1]);
// çıktı: İkinci
// çıktı: false
// çıktı: İkinci satır: ğüşıöç

Ekranda İ gibi karakterler görüyorsanız dosya büyük olasılıkla UTF-8'dir ama siz başka bir kodlamayla okuyorsunuz. İkinci tuzak satır sonlarıdır: Windows'ta oluşturulan dosyalarda satırlar \r\n ile biter. split('\n') kullanırsanız her satırın sonunda görünmeyen bir \r kalır. Düzenli ifade ile iki biçimi birden ayırmak daha güvenlidir:

javascript
import { readFile } from 'node:fs/promises';

const veri = await readFile('notlar.txt', 'utf8');
const satirlar = veri.split(/\r?\n/).filter((s) => s !== '');
console.log(satirlar.length);
console.log(satirlar.at(-1));
// çıktı: 3
// çıktı: Üçüncü satır

Hangi okuma yöntemi ne zaman?

Hangi okuma yöntemi ne zaman? fsPromises.readFile, fs.readFile, fs.readFileSync, fs.createReadStream
YöntemSonuçBellekUygun olduğu yer
fsPromises.readFilePromise ile tüm içerikDosyanın tamamıAsync kodda küçük ve orta dosyalar
fs.readFileCallback ile tüm içerikDosyanın tamamıCallback tabanlı eski kod, en düşük ek yük
fs.readFileSyncDoğrudan değer, hata fırlatırDosyanın tamamıBaşlangıçta bir kez okunan ayarlar, betikler
fs.createReadStreamParça parça (chunk)Birkaç parçaBüyük dosyalar, HTTP yanıtına aktarma
filehandle.readLinesSatır satırBirkaç parçaCSV, log gibi satır tabanlı dosyalar

Belge, en yüksek performans (çalışma süresi ve bellek ayırma açısından) gerektiğinde callback biçiminin Promise biçimine tercih edilebileceğini söyler. Günlük uygulama kodunda ise okunabilirlik nedeniyle çoğu ekip fs/promises ve async/await kullanır; async/await yazımız bu sözdizimini anlatıyor.

Sık yapılan hatalar

  • Kodlamayı unutmak: encoding vermeyince string yerine Buffer gelir ve JSON.parse gibi işlemler beklenmedik sonuç verir. Metin dosyalarında 'utf8' verin.
  • Göreli yolu betiğe göre sanmak: göreli yollar process.cwd() ile çözülür. Betiği başka bir klasörden çalıştırınca ENOENT alırsınız. Betiğe göre yol için import.meta.url ya da import.meta.dirname kullanın.
  • Sunucu isteğinde readFileSync kullanmak: her istekte olay döngüsü durur. İstek işleyicilerinde Promise ya da stream biçimini seçin.
  • Büyük dosyayı tek seferde okumak: bellek tüketimi dosya boyutu kadar artar, 2 GiB üzerinde ERR_FS_FILE_TOO_LARGE alınır. createReadStream ya da readLines() kullanın.
  • Hata mesajını karşılaştırmak: err.message.includes('no such file') yerine err.code === 'ENOENT' kontrol edin.
  • Önce varlık kontrolü yapmak: existsSync ardından readFile bir yarış durumu yaratır. Doğrudan okuyup hatayı ele alın.
  • Callback içinde hatayı yok saymak: if (err) kontrolü olmadan data kullanırsanız hata durumunda data tanımsız olur.

Sık Sorulan Sorular

readFile ile readFileSync arasındaki fark nedir?

İkisi de dosyanın tamamını okur. readFile işi arka planda yapar ve sonuç hazır olunca callback'i çağırır; bu sırada diğer kodlar çalışmaya devam eder. readFileSync ise okuma bitene kadar olay döngüsünü durdurur, değeri doğrudan döndürür ve hatayı fırlatır.

Dosyayı okurken neden &lt;Buffer ...&gt; görüyorum?

Kodlama belirtmediğiniz için Node.js ham baytları Buffer olarak döndürür. readFile(yol, 'utf8') yazarsanız string alırsınız. Elinizdeki Buffer'ı sonradan buf.toString('utf8') ile de çevirebilirsiniz.

Çok büyük bir dosyayı nasıl okurum?

fs.createReadStream() ile parça parça okuyun ve for await...of ile tüketin. Satır tabanlı dosyalarda filehandle.readLines() daha pratiktir. Veriyi başka bir hedefe (dosya, sıkıştırma, HTTP yanıtı) aktaracaksanız stream.pipeline kullanın.

Aynı klasördeki bir dosyayı nasıl güvenle okurum?

ESM'de new URL('./dosya.txt', import.meta.url) ya da path.join(import.meta.dirname, 'dosya.txt') kullanın. CommonJS'te aynı iş path.join(__dirname, 'dosya.txt') ile yapılır. Böylece betiği hangi klasörden çalıştırırsanız çalıştırın doğru dosya okunur.

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/promises, callback ve sync farkı · Stream nedir · readline ile girdi · Node.js nedir