TypeScript ile Node.js dosya işlemlerinde uzmanlaşın. Bu rehber, küresel ekipler için tür güvenliği, hata yönetimi ve en iyi uygulamalarla FS yöntemlerini ele alır.
TypeScript Dosya Sistemi Uzmanlığı: Küresel Geliştiriciler için Tür Güvenliğiyle Node.js Dosya İşlemleri
Modern yazılım geliştirmenin geniş dünyasında, Node.js ölçeklenebilir sunucu tarafı uygulamaları, komut satırı araçları ve daha fazlasını oluşturmak için güçlü bir çalışma zamanı olarak öne çıkmaktadır. Birçok Node.js uygulamasının temel bir yönü, dosya sistemiyle etkileşimi içerir – dosyaları ve dizinleri okuma, yazma, oluşturma ve yönetme. JavaScript bu işlemleri yönetme esnekliği sunarken, TypeScript'in tanıtımı, statik tür kontrolü, gelişmiş araçlar ve nihayetinde dosya sistemi kodunuza daha fazla güvenilirlik ve sürdürülebilirlik getirerek bu deneyimi yükseltir.
Bu kapsamlı rehber, kültürel geçmişleri veya coğrafi konumları ne olursa olsun, Node.js dosya işlemlerini TypeScript'in sunduğu sağlamlıkla öğrenmek isteyen küresel bir geliştirici kitlesi için hazırlanmıştır. Temel `fs` modülünü derinlemesine inceleyecek, çeşitli senkron ve asenkron paradigmalarını keşfedecek, modern promise tabanlı API'leri inceleyecek ve TypeScript'in tür sisteminin yaygın hataları önemli ölçüde nasıl azaltabileceğini ve kodunuzun okunabilirliğini nasıl artırabileceğini ortaya çıkaracağız.
Köşe Taşı: Node.js Dosya Sistemini Anlamak (`fs`)
Node.js `fs` modülü, dosya sistemiyle standart POSIX fonksiyonlarına göre modellenmiş bir şekilde etkileşim kurmak için bir API sağlar. Temel dosya okuma ve yazma işlemlerinden karmaşık dizin manipülasyonlarına ve dosya izlemeye kadar geniş bir yöntem yelpazesi sunar. Geleneksel olarak, bu işlemler geri çağrılar (callbacks) ile ele alınıyordu, bu da karmaşık senaryolarda kötü şöhretli "callback cehennemine" yol açıyordu. Node.js'in evrimiyle birlikte, promise'lar ve `async/await` asenkron işlemler için tercih edilen desenler olarak ortaya çıktı ve kodu daha okunabilir ve yönetilebilir hale getirdi.
Dosya Sistemi İşlemleri için Neden TypeScript?
Node.js'in `fs` modülü düz JavaScript ile mükemmel çalışsa da, TypeScript'i entegre etmek birçok çekici avantaj sağlar:
- Tür Güvenliği: Yanlış argüman türleri, eksik parametreler veya beklenmeyen dönüş değerleri gibi yaygın hataları, kodunuz çalışmadan önce derleme zamanında yakalar. Bu, özellikle çeşitli dosya kodlamaları, bayraklar ve `Buffer` nesneleriyle uğraşırken paha biçilmezdir.
- Gelişmiş Okunabilirlik: Açık tür ek açıklamaları, bir fonksiyonun ne tür veri beklediğini ve ne döndüreceğini netleştirerek, farklı ekiplerdeki geliştiriciler için kod anlama yeteneğini geliştirir.
- Daha İyi Araçlar ve Otomatik Tamamlama: IDE'ler (VS Code gibi) TypeScript'in tür tanımlarını kullanarak akıllı otomatik tamamlama, parametre ipuçları ve satır içi belgeler sağlayarak üretkenliği önemli ölçüde artırır.
- Yeniden Düzenleme Güveni: Bir arayüzü veya fonksiyon imzasını değiştirdiğinizde, TypeScript etkilenen tüm alanları anında işaretleyerek büyük ölçekli yeniden düzenlemeyi daha az hataya açık hale getirir.
- Küresel Tutarlılık: Uluslararası geliştirme ekipleri arasında tutarlı bir kodlama stili ve veri yapıları anlayışı sağlayarak belirsizliği azaltır.
Senkron ve Asenkron İşlemler: Küresel Bir Bakış Açısı
Senkron ve asenkron işlemler arasındaki ayrımı anlamak, özellikle performans ve yanıt verebilirliğin çok önemli olduğu küresel dağıtım için uygulamalar oluştururken hayati önem taşır. Çoğu `fs` modülü fonksiyonu senkron ve asenkron çeşitlerde gelir. Genel bir kural olarak, Node.js sunucunuzun yanıt verme yeteneğini sürdürmek için gerekli olan engellemeyen I/O işlemleri için asenkron yöntemler tercih edilir.
- Asenkron (Engellemeyen): Bu yöntemler son argüman olarak bir geri çağrı fonksiyonu alır veya bir `Promise` döndürür. Dosya sistemi işlemini başlatır ve hemen geri döner, diğer kodların çalışmasına izin verir. İşlem tamamlandığında, geri çağrı çağrılır (veya Promise çözülür/reddedilir). Bu, dünyanın dört bir yanındaki kullanıcılardan gelen birden çok eşzamanlı isteği işleyen sunucu uygulamaları için idealdir, çünkü sunucunun bir dosya işleminin bitmesini beklerken donmasını önler.
- Senkron (Engelleyen): Bu yöntemler, geri dönmeden önce işlemi tamamen gerçekleştirir. Kodlaması daha basit olsa da, Node.js olay döngüsünü engeller ve dosya sistemi işlemi bitene kadar başka hiçbir kodun çalışmasını önler. Bu, özellikle yüksek trafikli ortamlarda önemli performans darboğazlarına ve yanıt vermeyen uygulamalara yol açabilir. Bunları nadiren, genellikle uygulama başlangıç mantığı veya engellemenin kabul edilebilir olduğu basit betikler için kullanın.
TypeScript'te Temel Dosya İşlemi Türleri
TypeScript'in yaygın dosya sistemi işlemleriyle pratik uygulamalarına dalalım. Genellikle `@types/node` paketi aracılığıyla erişilebilen Node.js için yerleşik tür tanımlarını kullanacağız.
Başlamak için projenizde TypeScript ve Node.js türlerinin kurulu olduğundan emin olun:
npm install typescript @types/node --save-dev
`tsconfig.json` dosyanız aşağıdaki gibi uygun şekilde yapılandırılmalıdır, örneğin:
{
"compilerOptions": {
"target": "es2020",
"module": "commonjs",
"outDir": "./dist",
"strict": true,
"esModuleInterop": true,
"skipLibCheck": true,
"forceConsistentCasingInFileNames": true
},
"include": ["src/**/*"]
}
Dosyaları Okuma: `readFile`, `readFileSync` ve Promises API
Dosyalardan içerik okumak temel bir işlemdir. TypeScript, dosya yollarını, kodlamaları ve olası hataları doğru şekilde ele almanızı sağlar.
Asenkron Dosya Okuma (Geri Çağrı Tabanlı)
`fs.readFile` fonksiyonu, asenkron dosya okuma için temel işlevdir. Dosya yolunu, isteğe bağlı bir kodlamayı ve bir geri çağrı fonksiyonunu alır. TypeScript, geri çağrının argümanlarının (`Error | null`, `Buffer | string`) doğru şekilde türlenmesini sağlar.
import * as fs from 'fs';
const filePath: string = 'data/example.txt';
fs.readFile(filePath, 'utf8', (err: NodeJS.ErrnoException | null, data: string) => {
if (err) {
// Log error for international debugging, e.g., 'File not found'
console.error(`Error reading file '${filePath}': ${err.message}`);
return;
}
// Process file content, ensuring it's a string as per 'utf8' encoding
console.log(`File content (${filePath}):\n${data}`);
});
// Example: Reading binary data (no encoding specified)
const binaryFilePath: string = 'data/image.png';
fs.readFile(binaryFilePath, (err: NodeJS.ErrnoException | null, data: Buffer) => {
if (err) {
console.error(`Error reading binary file '${binaryFilePath}': ${err.message}`);
return;
}
// 'data' is a Buffer here, ready for further processing (e.g., streaming to a client)
console.log(`Read ${data.byteLength} bytes from ${binaryFilePath}`);
});
Senkron Dosya Okuma
`fs.readFileSync` olay döngüsünü engeller. Dönüş türü, bir kodlama sağlanıp sağlanmadığına bağlı olarak `Buffer` veya `string`'dir. TypeScript bunu doğru şekilde çıkarır.
import * as fs from 'fs';
const syncFilePath: string = 'data/sync_example.txt';
try {
const content: string = fs.readFileSync(syncFilePath, 'utf8');
console.log(`Synchronous read content (${syncFilePath}):\n${content}`);
} catch (error: any) {
console.error(`Synchronous read error for '${syncFilePath}': ${error.message}`);
}
Promise Tabanlı Dosya Okuma (`fs/promises`)
Modern `fs/promises` API'si, asenkron işlemler için şiddetle tavsiye edilen daha temiz, promise tabanlı bir arayüz sunar. TypeScript, özellikle `async/await` ile burada öne çıkar.
import * as fsPromises from 'fs/promises';
async function readTextFile(path: string): Promise
Dosyaları Yazma: `writeFile`, `writeFileSync` ve Bayraklar
Dosyalara veri yazmak da aynı derecede önemlidir. TypeScript, dosya yollarını, veri türlerini (string veya Buffer), kodlamayı ve dosya açma bayraklarını yönetmeye yardımcı olur.
Asenkron Dosya Yazma
`fs.writeFile`, bir dosyaya veri yazmak için kullanılır ve varsayılan olarak dosya zaten varsa onu değiştirir. Bu davranışı `flags` ile kontrol edebilirsiniz.
import * as fs from 'fs';
const outputFilePath: string = 'data/output.txt';
const fileContent: string = 'This is new content written by TypeScript.';
fs.writeFile(outputFilePath, fileContent, 'utf8', (err: NodeJS.ErrnoException | null) => {
if (err) {
console.error(`Error writing file '${outputFilePath}': ${err.message}`);
return;
}
console.log(`File '${outputFilePath}' written successfully.`);
});
// Example with Buffer data
const bufferContent: Buffer = Buffer.from('Binary data example');
const binaryOutputFilePath: string = 'data/binary_output.bin';
fs.writeFile(binaryOutputFilePath, bufferContent, (err: NodeJS.ErrnoException | null) => {
if (err) {
console.error(`Error writing binary file '${binaryOutputFilePath}': ${err.message}`);
return;
}
console.log(`Binary file '${binaryOutputFilePath}' written successfully.`);
});
Senkron Dosya Yazma
`fs.writeFileSync` yazma işlemi tamamlanana kadar olay döngüsünü engeller.
import * as fs from 'fs';
const syncOutputFilePath: string = 'data/sync_output.txt';
try {
fs.writeFileSync(syncOutputFilePath, 'Synchronously written content.', 'utf8');
console.log(`File '${syncOutputFilePath}' written synchronously.`);
} catch (error: any) {
console.error(`Synchronous write error for '${syncOutputFilePath}': ${error.message}`);
}
Promise Tabanlı Dosya Yazma (`fs/promises`)
`async/await` ve `fs/promises` ile modern yaklaşım, asenkron yazma işlemlerini yönetmek için genellikle daha temizdir.
import * as fsPromises from 'fs/promises';
import { constants as fsConstants } from 'fs'; // For flags
async function writeDataToFile(path: string, data: string | Buffer): Promise
Önemli Bayraklar:
- `'w'` (varsayılan): Yazma için dosyayı açar. Dosya oluşturulur (yoksa) veya kesilir (varsa).
- `'w+'`: Okuma ve yazma için dosyayı açar. Dosya oluşturulur (yoksa) veya kesilir (varsa).
- `'a'` (ekleme): Ekleme için dosyayı açar. Dosya yoksa oluşturulur.
- `'a+'`: Okuma ve ekleme için dosyayı açar. Dosya yoksa oluşturulur.
- `'r'` (okuma): Okuma için dosyayı açar. Dosya yoksa bir istisna oluşur.
- `'r+'`: Okuma ve yazma için dosyayı açar. Dosya yoksa bir istisna oluşur.
- `'wx'` (özel yazma): `'w'` gibidir ancak yol varsa başarısız olur.
- `'ax'` (özel ekleme): `'a'` gibidir ancak yol varsa başarısız olur.
Dosyalara Ekleme: `appendFile`, `appendFileSync`
Mevcut bir dosyanın içeriğini üzerine yazmadan sonuna veri eklemeniz gerektiğinde, tercihiniz `appendFile`'dır. Bu, özellikle günlükleme, veri toplama veya denetim izleri için kullanışlıdır.
Asenkron Ekleme
import * as fs from 'fs';
const logFilePath: string = 'data/app_logs.log';
function logMessage(message: string): void {
const timestamp: string = new Date().toISOString();
const logEntry: string = `${timestamp} - ${message}\n`;
fs.appendFile(logFilePath, logEntry, 'utf8', (err: NodeJS.ErrnoException | null) => {
if (err) {
console.error(`Error appending to log file '${logFilePath}': ${err.message}`);
return;
}
console.log(`Logged message to '${logFilePath}'.`);
});
}
logMessage('User "Alice" logged in.');
setTimeout(() => logMessage('System update initiated.'), 50);
logMessage('Database connection established.');
Senkron Ekleme
import * as fs from 'fs';
const syncLogFilePath: string = 'data/sync_app_logs.log';
function logMessageSync(message: string): void {
const timestamp: string = new Date().toISOString();
const logEntry: string = `${timestamp} - ${message}\n`;
try {
fs.appendFileSync(syncLogFilePath, logEntry, 'utf8');
console.log(`Logged message synchronously to '${syncLogFilePath}'.`);
} catch (error: any) {
console.error(`Synchronous error appending to log file '${syncLogFilePath}': ${error.message}`);
}
}
logMessageSync('Application started.');
logMessageSync('Configuration loaded.');
Promise Tabanlı Ekleme (`fs/promises`)
import * as fsPromises from 'fs/promises';
const promiseLogFilePath: string = 'data/promise_app_logs.log';
async function logMessagePromise(message: string): Promise
Dosyaları Silme: `unlink`, `unlinkSync`
Dosyaları dosya sisteminden kaldırma. TypeScript, geçerli bir yol ilettiğinizden ve hataları doğru şekilde yönettiğinizden emin olmanıza yardımcı olur.
Asenkron Silme
import * as fs from 'fs';
const fileToDeletePath: string = 'data/temp_to_delete.txt';
// First, create the file to ensure it exists for deletion demo
fs.writeFile(fileToDeletePath, 'Temporary content.', 'utf8', (err) => {
if (err) {
console.error('Error creating file for deletion demo:', err);
return;
}
console.log(`File '${fileToDeletePath}' created for deletion demo.`);
fs.unlink(fileToDeletePath, (err: NodeJS.ErrnoException | null) => {
if (err) {
console.error(`Error deleting file '${fileToDeletePath}': ${err.message}`);
return;
}
console.log(`File '${fileToDeletePath}' deleted successfully.`);
});
});
Senkron Silme
import * as fs from 'fs';
const syncFileToDeletePath: string = 'data/sync_temp_to_delete.txt';
try {
fs.writeFileSync(syncFileToDeletePath, 'Sync temp content.', 'utf8');
console.log(`File '${syncFileToDeletePath}' created.`);
fs.unlinkSync(syncFileToDeletePath);
console.log(`File '${syncFileToDeletePath}' deleted synchronously.`);
} catch (error: any) {
console.error(`Synchronous deletion error for '${syncFileToDeletePath}': ${error.message}`);
}
Promise Tabanlı Silme (`fs/promises`)
import * as fsPromises from 'fs/promises';
const promiseFileToDeletePath: string = 'data/promise_temp_to_delete.txt';
async function deleteFile(path: string): Promise
Dosya Varlığını ve İzinlerini Kontrol Etme: `existsSync`, `access`, `accessSync`
Bir dosya üzerinde işlem yapmadan önce, dosyanın var olup olmadığını veya mevcut sürecin gerekli izinlere sahip olup olmadığını kontrol etmeniz gerekebilir. TypeScript, `mode` parametresi için türler sağlayarak yardımcı olur.
Senkron Varlık Kontrolü
`fs.existsSync` basit, senkron bir kontroldür. Kullanışlı olsa da, bir yarış durumu güvenlik açığına sahiptir (bir dosya `existsSync` ile sonraki bir işlem arasında silinebilir), bu nedenle kritik işlemler için `fs.access` kullanmak genellikle daha iyidir.
import * as fs from 'fs';
const checkFilePath: string = 'data/example.txt';
if (fs.existsSync(checkFilePath)) {
console.log(`File '${checkFilePath}' exists.`);
} else {
console.log(`File '${checkFilePath}' does not exist.`);
}
Asenkron İzin Kontrolü (`fs.access`)
`fs.access`, `path` ile belirtilen dosya veya dizin için bir kullanıcının izinlerini test eder. Asenkrondur ve bir `mode` argümanı alır (örn. varlık için `fs.constants.F_OK`, okuma için `R_OK`, yazma için `W_OK`, yürütme için `X_OK`).
import * as fs from 'fs';
import { constants } from 'fs';
const accessFilePath: string = 'data/example.txt';
fs.access(accessFilePath, constants.F_OK, (err: NodeJS.ErrnoException | null) => {
if (err) {
console.error(`File '${accessFilePath}' does not exist or access denied.`);
return;
}
console.log(`File '${accessFilePath}' exists.`);
});
fs.access(accessFilePath, constants.R_OK | constants.W_OK, (err: NodeJS.ErrnoException | null) => {
if (err) {
console.error(`File '${accessFilePath}' is not readable/writable or access denied: ${err.message}`);
return;
}
console.log(`File '${accessFilePath}' is readable and writable.`);
});
Promise Tabanlı İzin Kontrolü (`fs/promises`)
import * as fsPromises from 'fs/promises';
import { constants } from 'fs';
async function checkFilePermissions(path: string, mode: number): Promise
Dosya Bilgisi Alma: `stat`, `statSync`, `fs.Stats`
`fs.stat` fonksiyon ailesi, bir dosya veya dizin hakkında boyut, oluşturma tarihi, değiştirme tarihi ve izinler gibi ayrıntılı bilgi sağlar. TypeScript'in `fs.Stats` arayüzü, bu verilerle çalışmayı son derece yapılandırılmış ve güvenilir hale getirir.
Asenkron Stat
import * as fs from 'fs';
import { Stats } from 'fs';
const statFilePath: string = 'data/example.txt';
fs.stat(statFilePath, (err: NodeJS.ErrnoException | null, stats: Stats) => {
if (err) {
console.error(`Error getting stats for '${statFilePath}': ${err.message}`);
return;
}
console.log(`Stats for '${statFilePath}':`);
console.log(` Is file: ${stats.isFile()}`);
console.log(` Is directory: ${stats.isDirectory()}`);
console.log(` Size: ${stats.size} bytes`);
console.log(` Creation time: ${stats.birthtime.toISOString()}`);
console.log(` Last modified: ${stats.mtime.toISOString()}`);
});
Promise Tabanlı Stat (`fs/promises`)
import * as fsPromises from 'fs/promises';
import { Stats } from 'fs'; // Still use the 'fs' module's Stats interface
async function getFileStats(path: string): Promise
TypeScript ile Dizin İşlemleri
Dizinleri yönetmek, dosyaları düzenlemek, uygulamaya özel depolama oluşturmak veya geçici verileri işlemek için yaygın bir gereksinimdir. TypeScript, bu işlemler için sağlam türleme sağlar.
Dizin Oluşturma: `mkdir`, `mkdirSync`
`fs.mkdir` fonksiyonu yeni dizinler oluşturmak için kullanılır. `recursive` seçeneği, üst dizinler zaten mevcut değilse onları oluşturmak için inanılmaz derecede kullanışlıdır, Unix benzeri sistemlerdeki `mkdir -p` davranışını taklit eder.
Asenkron Dizin Oluşturma
import * as fs from 'fs';
const newDirPath: string = 'data/new_directory';
const recursiveDirPath: string = 'data/nested/path/to/create';
// Create a single directory
fs.mkdir(newDirPath, (err: NodeJS.ErrnoException | null) => {
if (err) {
// Ignore EEXIST error if directory already exists
if (err.code === 'EEXIST') {
console.log(`Directory '${newDirPath}' already exists.`);
} else {
console.error(`Error creating directory '${newDirPath}': ${err.message}`);
}
return;
}
console.log(`Directory '${newDirPath}' created successfully.`);
});
// Create nested directories recursively
fs.mkdir(recursiveDirPath, { recursive: true }, (err: NodeJS.ErrnoException | null) => {
if (err) {
if (err.code === 'EEXIST') {
console.log(`Directory '${recursiveDirPath}' already exists.`);
} else {
console.error(`Error creating recursive directory '${recursiveDirPath}': ${err.message}`);
}
return;
}
console.log(`Recursive directories '${recursiveDirPath}' created successfully.`);
});
Promise Tabanlı Dizin Oluşturma (`fs/promises`)
import * as fsPromises from 'fs/promises';
async function createDirectory(path: string, recursive: boolean = false): Promise
Dizin İçeriğini Okuma: `readdir`, `readdirSync`, `fs.Dirent`
Belirli bir dizindeki dosyaları ve alt dizinleri listelemek için `fs.readdir` kullanırsınız. `withFileTypes` seçeneği, her girişi ayrı ayrı `stat` yapmaya gerek kalmadan doğrudan daha ayrıntılı bilgi sağlayan `fs.Dirent` nesnelerini döndüren modern bir eklentidir.
Asenkron Dizin Okuma
import * as fs from 'fs';
const readDirPath: string = 'data';
fs.readdir(readDirPath, (err: NodeJS.ErrnoException | null, files: string[]) => {
if (err) {
console.error(`Error reading directory '${readDirPath}': ${err.message}`);
return;
}
console.log(`Contents of directory '${readDirPath}':`);
files.forEach(file => {
console.log(` - ${file}`);
});
});
// With `withFileTypes` option
fs.readdir(readDirPath, { withFileTypes: true }, (err: NodeJS.ErrnoException | null, dirents: fs.Dirent[]) => {
if (err) {
console.error(`Error reading directory with file types '${readDirPath}': ${err.message}`);
return;
}
console.log(`Contents of directory '${readDirPath}' (with types):`);
dirents.forEach(dirent => {
const type: string = dirent.isFile() ? 'File' : dirent.isDirectory() ? 'Directory' : 'Other';
console.log(` - ${dirent.name} (${type})`);
});
});
Promise Tabanlı Dizin Okuma (`fs/promises`)
import * as fsPromises from 'fs/promises';
import { Dirent } from 'fs'; // Still use 'fs' module's Dirent interface
async function listDirectoryContents(path: string): Promise
Dizinleri Silme: `rmdir` (kullanımdan kaldırıldı), `rm`, `rmSync`
Node.js, dizin silme yöntemlerini geliştirmiştir. `fs.rmdir` artık özyinelemeli silme işlemleri için büyük ölçüde `fs.rm` tarafından geçilmiştir ve daha sağlam ve tutarlı bir API sunmaktadır.
Asenkron Dizin Silme (`fs.rm`)
`fs.rm` fonksiyonu (Node.js 14.14.0'dan beri mevcuttur), dosya ve dizinleri kaldırmak için önerilen yöntemdir. `recursive: true` seçeneği, boş olmayan dizinleri silmek için çok önemlidir.
import * as fs from 'fs';
const dirToDeletePath: string = 'data/dir_to_delete';
const nestedDirToDeletePath: string = 'data/nested_dir/sub';
// Setup: Create a directory with a file inside for recursive deletion demo
fs.mkdir(nestedDirToDeletePath, { recursive: true }, (err) => {
if (err && err.code !== 'EEXIST') {
console.error('Error creating nested directory for demo:', err);
return;
}
fs.writeFile(`${nestedDirToDeletePath}/file_inside.txt`, 'Some content', (err) => {
if (err) { console.error('Error creating file inside nested directory:', err); return; }
console.log(`Directory '${nestedDirToDeletePath}' and file created for deletion demo.`);
fs.rm(nestedDirToDeletePath, { recursive: true, force: true }, (err: NodeJS.ErrnoException | null) => {
if (err) {
console.error(`Error deleting recursive directory '${nestedDirToDeletePath}': ${err.message}`);
return;
}
console.log(`Recursive directory '${nestedDirToDeletePath}' deleted successfully.`);
});
});
});
// Deleting an empty directory
fs.mkdir(dirToDeletePath, (err) => {
if (err && err.code !== 'EEXIST') {
console.error('Error creating empty directory for demo:', err);
return;
}
console.log(`Directory '${dirToDeletePath}' created for deletion demo.`);
fs.rm(dirToDeletePath, { recursive: false }, (err: NodeJS.ErrnoException | null) => {
if (err) {
console.error(`Error deleting empty directory '${dirToDeletePath}': ${err.message}`);
return;
}
console.log(`Empty directory '${dirToDeletePath}' deleted successfully.`);
});
});
Promise Tabanlı Dizin Silme (`fs/promises`)
import * as fsPromises from 'fs/promises';
async function deleteDirectory(path: string, recursive: boolean = false): Promise
TypeScript ile Gelişmiş Dosya Sistemi Kavramları
Temel okuma/yazma işlemlerinin ötesinde, Node.js daha büyük dosyaları, sürekli veri akışlarını ve dosya sisteminin gerçek zamanlı izlenmesini yönetmek için güçlü özellikler sunar. TypeScript'in tür bildirimleri, bu gelişmiş senaryolara sorunsuz bir şekilde genişleyerek sağlamlık sağlar.
Dosya Tanımlayıcıları ve Akışları
Çok büyük dosyalar için veya dosya erişimi üzerinde ayrıntılı kontrol (örn. bir dosya içindeki belirli konumlar) gerektiğinde, dosya tanımlayıcıları ve akışlar vazgeçilmez hale gelir. Akışlar, büyük miktardaki veriyi parçalar halinde okuma veya yazma işlemlerini verimli bir şekilde yönetmenin etkili bir yolunu sunar; bu da ölçeklenebilir uygulamalar ve sunucularda küresel olarak verimli kaynak yönetimi için kritik öneme sahiptir.
Tanımlayıcılarla Dosya Açma ve Kapatma (`fs.open`, `fs.close`)
Bir dosya tanımlayıcı, işletim sistemi tarafından açık bir dosyaya atanan benzersiz bir tanımlayıcıdır (bir sayı). Bir dosya tanımlayıcı almak için `fs.open` kullanabilir, ardından bu tanımlayıcıyı kullanarak `fs.read` veya `fs.write` gibi işlemler gerçekleştirebilir ve son olarak `fs.close` ile kapatabilirsiniz.
import * as fs from 'fs';
import { promises as fsPromises } from 'fs';
import { constants } from 'fs';
const descriptorFilePath: string = 'data/descriptor_example.txt';
async function demonstrateFileDescriptorOperations(): Promise
Dosya Akışları (`fs.createReadStream`, `fs.createWriteStream`)
Akışlar, büyük dosyaları verimli bir şekilde işlemek için güçlüdür. `fs.createReadStream` ve `fs.createWriteStream` sırasıyla `Readable` ve `Writable` akışları döndürür ve bunlar Node.js'in akış API'siyle sorunsuz bir şekilde entegre olur. TypeScript, bu akış olayları için (örn. `'data'`, `'end'`, `'error'`) mükemmel tür tanımları sağlar.
import * as fs from 'fs';
const largeFilePath: string = 'data/large_file.txt';
const copiedFilePath: string = 'data/copied_file.txt';
// Create a dummy large file for demonstration
function createLargeFile(path: string, sizeInMB: number): void {
const content: string = 'Lorem ipsum dolor sit amet, consectetur adipiscing elit. '; // 56 chars
const stream = fs.createWriteStream(path);
const totalChars = sizeInMB * 1024 * 1024; // Convert MB to bytes
const iterations = Math.ceil(totalChars / content.length);
for (let i = 0; i < iterations; i++) {
stream.write(content);
}
stream.end(() => console.log(`Created large file '${path}' (${sizeInMB}MB).`));
}
// For demonstration, let's ensure the 'data' directory exists first
fs.mkdir('data', { recursive: true }, (err) => {
if (err && err.code !== 'EEXIST') {
console.error('Error creating data directory:', err);
return;
}
createLargeFile(largeFilePath, 1); // Create a 1MB file
});
// Copy file using streams
function copyFileWithStreams(source: string, destination: string): void {
const readStream = fs.createReadStream(source);
const writeStream = fs.createWriteStream(destination);
readStream.on('open', () => console.log(`Reading stream for '${source}' opened.`));
writeStream.on('open', () => console.log(`Writing stream for '${destination}' opened.`));
// Pipe data from read stream to write stream
readStream.pipe(writeStream);
readStream.on('error', (err: Error) => {
console.error(`Read stream error: ${err.message}`);
});
writeStream.on('error', (err: Error) => {
console.error(`Write stream error: ${err.message}`);
});
writeStream.on('finish', () => {
console.log(`File '${source}' copied to '${destination}' successfully using streams.`);
// Clean up dummy large file after copy
fs.unlink(largeFilePath, (err) => {
if (err) console.error('Error deleting large file:', err);
else console.log(`Large file '${largeFilePath}' deleted.`);
});
});
}
// Wait a bit for the large file to be created before attempting to copy
setTimeout(() => {
copyFileWithStreams(largeFilePath, copiedFilePath);
}, 1000);
Değişiklikleri İzleme: `fs.watch`, `fs.watchFile`
Dosya sistemini değişiklikler için izlemek, hot-reloading geliştirme sunucuları, derleme süreçleri veya gerçek zamanlı veri senkronizasyonu gibi görevler için hayati öneme sahiptir. Node.js bunun için iki temel yöntem sunar: `fs.watch` ve `fs.watchFile`. TypeScript, olay türlerinin ve dinleyici parametrelerinin doğru şekilde işlenmesini sağlar.
`fs.watch`: Olay Tabanlı Dosya Sistemi İzleme
`fs.watch` genellikle daha verimlidir, çünkü genellikle işletim sistemi düzeyinde bildirimleri (örn. Linux'ta `inotify`, macOS'ta `kqueue`, Windows'ta `ReadDirectoryChangesW`) kullanır. Belirli dosya veya dizinleri değişiklikler, silmeler veya yeniden adlandırmalar için izlemek için uygundur.
import * as fs from 'fs';
const watchedFilePath: string = 'data/watched_file.txt';
const watchedDirPath: string = 'data/watched_dir';
// Ensure files/directories exist for watching
fs.writeFileSync(watchedFilePath, 'Initial content.');
fs.mkdirSync(watchedDirPath, { recursive: true });
console.log(`Watching '${watchedFilePath}' for changes...`);
const fileWatcher = fs.watch(watchedFilePath, (eventType: string, filename: string | Buffer | null) => {
const fname = typeof filename === 'string' ? filename : filename?.toString('utf8');
console.log(`File '${fname || 'N/A'}' event: ${eventType}`);
if (eventType === 'change') {
console.log('File content potentially changed.');
}
// In a real application, you might read the file here or trigger a rebuild
});
console.log(`Watching directory '${watchedDirPath}' for changes...`);
const dirWatcher = fs.watch(watchedDirPath, (eventType: string, filename: string | Buffer | null) => {
const fname = typeof filename === 'string' ? filename : filename?.toString('utf8');
console.log(`Directory '${watchedDirPath}' event: ${eventType} on '${fname || 'N/A'}'`);
});
fileWatcher.on('error', (err: Error) => console.error(`File watcher error: ${err.message}`));
dirWatcher.on('error', (err: Error) => console.error(`Directory watcher error: ${err.message}`));
// Simulate changes after a delay
setTimeout(() => {
console.log('\n--- Simulating changes ---');
fs.appendFileSync(watchedFilePath, '\nNew line added.');
fs.writeFileSync(`${watchedDirPath}/new_file.txt`, 'Content.');
fs.unlinkSync(`${watchedDirPath}/new_file.txt`); // Also test deletion
setTimeout(() => {
fileWatcher.close();
dirWatcher.close();
console.log('\nWatchers closed.');
// Clean up temporary files/dirs
fs.unlinkSync(watchedFilePath);
fs.rmSync(watchedDirPath, { recursive: true, force: true });
}, 2000);
}, 1000);
`fs.watch` Hakkında Not: Tüm platformlarda tüm olay türleri için her zaman güvenilir değildir (örn. dosya yeniden adlandırmaları silme ve oluşturma olarak rapor edilebilir). Sağlam çapraz platform dosya izleme için, genellikle `fs.watch`'ı kullanan ancak normalleştirme ve geri dönüş mekanizmaları ekleyen `chokidar` gibi kütüphaneleri düşünün.
`fs.watchFile`: Yoklama Tabanlı Dosya İzleme
`fs.watchFile`, değişiklikleri algılamak için yoklama (dosyanın `stat` verilerini periyodik olarak kontrol etme) kullanır. Daha az verimli ancak farklı dosya sistemleri ve ağ sürücüleri arasında daha tutarlıdır. `fs.watch`'ın güvenilir olmayabileceği ortamlara (örn. NFS paylaşımları) daha uygundur.
import * as fs from 'fs';
import { Stats } from 'fs';
const pollFilePath: string = 'data/polled_file.txt';
fs.writeFileSync(pollFilePath, 'Initial polled content.');
console.log(`Polling '${pollFilePath}' for changes...`);
fs.watchFile(pollFilePath, { interval: 1000 }, (curr: Stats, prev: Stats) => {
// TypeScript ensures 'curr' and 'prev' are fs.Stats objects
if (curr.mtimeMs !== prev.mtimeMs) {
console.log(`File '${pollFilePath}' modified (mtime changed). New size: ${curr.size} bytes.`);
}
});
setTimeout(() => {
console.log('\n--- Simulating polled file change ---');
fs.appendFileSync(pollFilePath, '\nAnother line added to polled file.');
setTimeout(() => {
fs.unwatchFile(pollFilePath);
console.log(`\nStopped watching '${pollFilePath}'.`);
fs.unlinkSync(pollFilePath);
}, 2000);
}, 1500);
Küresel Bir Bağlamda Hata Yönetimi ve En İyi Uygulamalar
Sağlam hata yönetimi, herhangi bir üretime hazır uygulama için, özellikle dosya sistemiyle etkileşimde olanlar için çok önemlidir. Dosya işlemleri birçok nedenden dolayı başarısız olabilir: izin sorunları, disk dolu hataları, dosya bulunamadı, G/Ç hataları, ağ sorunları (ağ sürücüleri için) veya eşzamanlı erişim çakışmaları. TypeScript, türle ilgili sorunları yakalamanıza yardımcı olur, ancak çalışma zamanı hataları yine de dikkatli yönetim gerektirir.
Hata Yönetimi Stratejileri
- Senkron İşlemler: Her zaman `fs.xxxSync` çağrılarını `try...catch` blokları içine alın. Bu yöntemler doğrudan hatalar fırlatır.
- Asenkron Geri Çağrılar: Bir `fs` geri çağrısının ilk argümanı her zaman `err: NodeJS.ErrnoException | null` şeklindedir. Her zaman önce bu `err` nesnesini kontrol edin.
- Promise Tabanlı (`fs/promises`): Reddedmeleri işlemek için `await` ile `try...catch` veya `.then()` zincirleri ile `.catch()` kullanın.
Uygulamanızın hata geri bildirimi kullanıcıya dönükse, hata günlüğü formatlarını standartlaştırmak ve hata mesajları için uluslararasılaştırmayı (i18n) düşünmek faydalıdır.
import * as fs from 'fs';
import { promises as fsPromises } from 'fs';
import * as path from 'path';
const problematicPath = path.join('non_existent_dir', 'file.txt');
// Synchronous error handling
try {
fs.readFileSync(problematicPath, 'utf8');
} catch (error: any) {
console.error(`Sync Error: ${error.code} - ${error.message} (Path: ${problematicPath})`);
}
// Callback-based error handling
fs.readFile(problematicPath, 'utf8', (err, data) => {
if (err) {
console.error(`Callback Error: ${err.code} - ${err.message} (Path: ${problematicPath})`);
return;
}
// ... process data
});
// Promise-based error handling
async function safeReadFile(filePath: string): Promise
Kaynak Yönetimi: Dosya Tanımlayıcılarını Kapatma
`fs.open` (veya `fsPromises.open`) ile çalışırken, işlemler tamamlandıktan sonra, hatalar meydana gelse bile dosya tanımlayıcılarının her zaman `fs.close` (veya `fileHandle.close()`) kullanılarak kapatılması kritik öneme sahiptir. Bunu yapmamak, kaynak sızıntılarına, işletim sisteminin açık dosya sınırına ulaşmaya ve uygulamanızın çökmesine veya diğer süreçleri etkilemesine yol açabilir.
`FileHandle` nesneleriyle `fs/promises` API'si genellikle bunu basitleştirir, çünkü `fileHandle.close()` bu amaç için özel olarak tasarlanmıştır ve `FileHandle` örnekleri `Disposable`'dır (Node.js 18.11.0+ ve TypeScript 5.2+ kullanılıyorsa).
Yol Yönetimi ve Çapraz Platform Uyumluluğu
Dosya yolları işletim sistemleri arasında önemli ölçüde farklılık gösterir (örn. Windows'ta `\`, Unix benzeri sistemlerde `/`). Node.js `path` modülü, küresel dağıtımlar için hayati önem taşıyan, çapraz platform uyumlu bir şekilde dosya yollarını oluşturmak ve ayrıştırmak için vazgeçilmezdir.
- `path.join(...paths)`: Verilen tüm yol segmentlerini birleştirir ve ortaya çıkan yolu normalleştirir.
- `path.resolve(...paths)`: Bir yol dizisini veya yol segmentlerini mutlak bir yola çözer.
- `path.basename(path)`: Bir yolun son kısmını döndürür.
- `path.dirname(path)`: Bir yolun dizin adını döndürür.
- `path.extname(path)`: Yolun uzantısını döndürür.
TypeScript, `path` modülü için tam tür tanımları sağlayarak, fonksiyonlarını doğru şekilde kullandığınızdan emin olmanızı sağlar.
import * as path from 'path';
const dir = 'my_app_data';
const filename = 'config.json';
// Cross-platform path joining
const fullPath: string = path.join(__dirname, dir, filename);
console.log(`Cross-platform path: ${fullPath}`);
// Get directory name
const dirname: string = path.dirname(fullPath);
console.log(`Directory name: ${dirname}`);
// Get base file name
const basename: string = path.basename(fullPath);
console.log(`Base name: ${basename}`);
// Get file extension
const extname: string = path.extname(fullPath);
console.log(`Extension: ${extname}`);
Eşzamanlılık ve Yarış Durumları
Birden fazla asenkron dosya işlemi eşzamanlı olarak başlatıldığında, özellikle yazma veya silme işlemleri, yarış durumları meydana gelebilir. Örneğin, bir işlem bir dosyanın varlığını kontrol eder ve diğer bir işlem ilk işlem devreye girmeden onu silerse, ilk işlem beklenmedik bir şekilde başarısız olabilir.
- Kritik yol mantığı için `fs.existsSync` kullanmaktan kaçının; `fs.access` tercih edin veya sadece işlemi deneyip hatayı işleyin.
- Özel erişim gerektiren işlemler için uygun `flag` seçeneklerini kullanın (örn. özel yazma için `'wx'`).
- Yüksek derecede kritik paylaşılan kaynak erişimi için kilitleme mekanizmaları (örn. dosya kilitleri veya uygulama düzeyinde kilitler) uygulayın, ancak bu karmaşıklık ekler.
İzinler (ACL'ler)
Dosya sistemi izinleri (Erişim Kontrol Listeleri veya standart Unix izinleri) yaygın bir hata kaynağıdır. Node.js sürecinizin dosyaları ve dizinleri okumak, yazmak veya yürütmek için gerekli izinlere sahip olduğundan emin olun. Bu, özellikle konteynerli ortamlarda veya süreçlerin belirli kullanıcı hesaplarıyla çalıştığı çok kullanıcılı sistemlerde geçerlidir.
Sonuç: Küresel Dosya Sistemi İşlemleri için Tür Güvenliğini Benimsemek
Node.js `fs` modülü, dosya sistemiyle etkileşim kurmak için güçlü ve çok yönlü bir araçtır; temel dosya manipülasyonlarından gelişmiş akış tabanlı veri işlemeye kadar bir dizi seçenek sunar. Bu işlemlerin üzerine TypeScript katmanı ekleyerek paha biçilmez faydalar elde edersiniz: derleme zamanı hata tespiti, gelişmiş kod netliği, üstün araç desteği ve yeniden düzenleme sırasında artan güven. Bu, farklı kod tabanlarında tutarlılığın ve azaltılmış belirsizliğin hayati olduğu küresel geliştirme ekipleri için özellikle önemlidir.
İster küçük bir yardımcı betik, ister büyük ölçekli bir kurumsal uygulama geliştiriyor olun, Node.js dosya işlemleriniz için TypeScript'in sağlam tür sistemini kullanmak, daha sürdürülebilir, güvenilir ve hataya dayanıklı bir kodla sonuçlanacaktır. Daha temiz asenkron desenler için `fs/promises` API'sini benimseyin, senkron ve asenkron çağrılar arasındaki nüansları anlayın ve her zaman sağlam hata yönetimine ve çapraz platform yol yönetimine öncelik verin.
Bu rehberde ele alınan prensipleri ve örnekleri uygulayarak, dünya genelindeki geliştiriciler sadece performanslı ve verimli değil, aynı zamanda doğası gereği daha güvenli ve anlaşılması daha kolay dosya sistemi etkileşimleri oluşturabilir, nihayetinde daha yüksek kalitede yazılım teslimatlarına katkıda bulunabilirler.