Kodax

File System (fs) — Node.js و Express

نظام الملفات (fs)

وحدة fs تقرأ الملفات وتكتبها وتدير المجلدات. لها ثلاث واجهات — استخدم الحديثة.

| الواجهة | مثال | متى |

|---|---|---|

| Promises | await fs.readFile(...) من node:fs/promises | ✅ الافتراضي |

| Callbacks | fs.readFile(path, cb) | كود قديم |

| متزامنة | fs.readFileSync(...) | سكربتات صغيرة وعند بدء التشغيل فقط — لا في معالجة الطلبات (تجمّد الخادم) |

القراءة والكتابة

const fs = require("node:fs/promises");
const path = require("node:path");
const os = require("node:os");

async function main() {
  const file = path.join(os.tmpdir(), "kodax-notes.txt");
  await fs.writeFile(file, "السطر الأول\n", "utf8");     // ينشئ أو يستبدل
  await fs.appendFile(file, "السطر الثاني\n", "utf8");   // يضيف للنهاية
  const text = await fs.readFile(file, "utf8");          // بدون "utf8" تحصل على Buffer
  console.log(text.trim().split("\n"));
  await fs.unlink(file);                                  // حذف
}

main().catch((err) => console.error("فشل:", err.message));

معالجة الأخطاء

الملف قد لا يوجد، أو بلا صلاحيات. افحص err.code:

const fs = require("node:fs/promises");

async function readConfig() {
  try {
    return JSON.parse(await fs.readFile("config-missing.json", "utf8"));
  } catch (err) {
    if (err.code === "ENOENT") return { theme: "light" };   // غير موجود ← افتراضي
    throw err;                                               // غيره: لا نخفيه
  }
}
readConfig().then((c) => console.log(c));

رموز شائعة: ENOENT (غير موجود)، EACCES (لا صلاحية)، EEXIST (موجود مسبقاً).

المجلدات

fs.mkdir(dir, { recursive: true }) ينشئ المسار كاملاً ولا يفشل إن وُجد. fs.readdir(dir) يسرد المحتويات، و fs.rm(dir, { recursive: true, force: true }) يحذف مجلداً بمحتواه (بحذر!).

المسارات: `__dirname` و path

المسار النسبي في fs نسبةً إلى مكان تشغيل الأمر لا مكان الملف. للدقة: path.join(__dirname, "data", "users.json").

⚠️ أمان: لا تبنِ المسار من مدخلات المستخدم مباشرة

const path = require("node:path");

const UPLOADS = path.resolve("uploads");
function safePath(userFileName) {
  const full = path.resolve(UPLOADS, userFileName);
  if (!full.startsWith(UPLOADS + path.sep)) throw new Error("مسار غير مسموح");
  return full;
}
console.log(safePath("avatar.png"));
try { safePath("../../etc/passwd"); } catch (e) { console.log(e.message); }

بدون هذا الفحص يستطيع مهاجم طلب ../../ والوصول لملفات خارج المجلد (Path Traversal).

الملفات الكبيرة: Streams

readFile يحمّل الملف كله في الذاكرة. لملف 2GB استخدم التدفقات: fs.createReadStream تقرأ قطعة قطعة، و pipeline من node:stream/promises تربط القراءة بالكتابة أو الضغط بأمان.

تمرين

اكتب سكربت notes.js: add "نص" يضيف ملاحظة بتاريخها إلى notes.json (أنشئه إن لم يوجد)، و list يطبعها. استخدم process.argv للأوامر و fs/promises للملفات.

const fs = require("node:fs/promises");
const path = require("node:path");
const os = require("node:os");

async function main() {
  // مجلد مؤقت للتجربة (لا نلمس ملفات المشروع)
  const dir = await fs.mkdtemp(path.join(os.tmpdir(), "kodax-fs-"));
  const notesFile = path.join(dir, "notes.json");

  // ===== قراءة مع قيمة افتراضية إن لم يوجد الملف =====
  async function loadNotes() {
    try {
      return JSON.parse(await fs.readFile(notesFile, "utf8"));
    } catch (err) {
      if (err.code === "ENOENT") return [];
      throw err;
    }
  }

  async function addNote(text) {
    const notes = await loadNotes();
    notes.push({ text, at: new Date().toISOString() });
    await fs.writeFile(notesFile, JSON.stringify(notes, null, 2), "utf8");
  }

  await addNote("تعلم fs/promises");
  await addNote("لا تستخدم readFileSync داخل معالجة الطلبات");

  const notes = await loadNotes();
  notes.forEach((n, i) => console.log(`${i + 1}. ${n.text}`));

  // ===== المجلد =====
  await fs.mkdir(path.join(dir, "archive", "2026"), { recursive: true });
  console.log("محتويات المجلد:", await fs.readdir(dir));

  // ===== التنظيف =====
  await fs.rm(dir, { recursive: true, force: true });
  console.log("تم حذف المجلد المؤقت");
}

main().catch((err) => {
  console.error("فشل:", err.message);
  process.exitCode = 1;
});
اختبر فهمك