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;
});اختبر فهمك