مقدمة: لماذا العمل مع نظام الملفات مهم في Node.js
في تطوير الويب الحديث، نادرًا ما يمكن الاستغناء عن التفاعل مع نظام الملفات. سواء كان تحميل الصور الرمزية للمستخدمين، أو إنشاء سجلات، أو معالجة تقارير CSV، أو قراءة ملفات التكوين - يوفر Node.js وحدة مدمجة قوية fs (نظام الملفات) لهذه المهام.
على عكس JavaScript في المتصفح، حيث يكون الوصول إلى الملفات مقيدًا بشدة لأغراض أمنية، يمنحك Node.js تحكمًا كاملاً في نظام ملفات الخادم. في هذه المقالة، سنشرح العمليات الرئيسية بأمثلة عملية ستساعدك على العمل بثقة مع الملفات والمجلدات.
ما ستتعلمه:
- كيفية قراءة وكتابة الملفات (بشكل متزامن وغير متزامن)
- كيفية العمل مع التدفقات (Streams) للملفات الكبيرة
- كيفية إنشاء وإعادة تسمية وحذف المجلدات
- كيفية معالجة الأخطاء والمسارات بشكل صحيح
1. الأساسيات: قراءة وكتابة الملفات
تقدم وحدة fs ثلاثة أساليب رئيسية للعمل مع الملفات: المتزامن، وغير المتزامن (مع دوال الاسترجاع)، والوعد. يُوصى باستخدام النهج غير المتزامن القائم على الوعود (fs.promises) - فهو لا يحجب Event Loop وأسهل في القراءة.
1.1 قراءة ملف
أبسط طريقة لقراءة ملف بالكامل هي استخدام readFile.
const fs = require('fs').promises;
async function readConfig() { try { const data = await fs.readFile('./config.json', 'utf-8'); const config = JSON.parse(data); console.log('المنفذ:', config.port); } catch (err) { console.error('خطأ في القراءة:', err.message); }}
readConfig();هام: حدد الترميز ('utf-8')، وإلا فسيتم إرجاع كائن Buffer.
1.2 الكتابة في ملف
للكتابة، استخدم writeFile (يعيد كتابة الملف) أو appendFile (يضيف في النهاية).
const fs = require('fs').promises;
async function saveLog(message) { const logLine = `[${new Date().toISOString()}] ${message}`; try { await fs.appendFile('./app.log', logLine); console.log('تم حفظ السجل'); } catch (err) { console.error('خطأ في الكتابة:', err); }}
saveLog('تم تشغيل الخادم');2. العمل مع الملفات الكبيرة: التدفقات (Streams)
تخيل أنك بحاجة لقراءة ملف بحجم 2 جيجابايت. طريقة readFile ستحمله بالكامل في الذاكرة العشوائية، مما قد يؤدي إلى تسرب الذاكرة. لمثل هذه الحالات، توجد التدفقات (fs.createReadStream و fs.createWriteStream).
يعالج التدفق البيانات على أجزاء (chunks)، دون تخزين الملف بأكمله في الذاكرة.
مثال: نسخ ملف كبير
const fs = require('fs');const path = require('path');
const source = path.join(__dirname, 'bigfile.zip');const destination = path.join(__dirname, 'copy.zip');
const readStream = fs.createReadStream(source);const writeStream = fs.createWriteStream(destination);
readStream.on('data', (chunk) => { console.log(`تمت قراءة ${chunk.length} بايت`); writeStream.write(chunk);});
readStream.on('end', () => { writeStream.end(); console.log('اكتملت عملية النسخ');});
readStream.on('error', (err) => { console.error('خطأ في القراءة:', err);});نصيحة: للنسخ، يُفضل استخدام الطريقة المدمجة pipeline من وحدة stream - فهي تعالج الأخطاء تلقائيًا وتغلق التدفقات.
3. إدارة الدلائل
العمل مع المجلدات ليس أقل أهمية. يسمح Node.js بإنشاء وقراءة وحذف الدلائل.
3.1 إنشاء والتحقق من وجود مجلد
const fs = require('fs').promises;
async function ensureDir(dirPath) { try { await fs.mkdir(dirPath, { recursive: true }