Введение: почему работа с файловой системой важна в Node.js
В современной веб-разработке редко можно обойтись без взаимодействия с файловой системой. Будь то загрузка аватаров пользователей, генерация логов, обработка CSV-отчетов или чтение конфигурационных файлов — Node.js предоставляет мощный встроенный модуль fs (File System) для этих задач.
В отличие от браузерного 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}\n`;
try {
await fs.appendFile('./app.log', logLine);
console.log('Лог сохранен');
} catch (err) {
console.error('Ошибка записи:', err);
}
}
saveLog('Сервер запущен');
2. Работа с большими файлами: Streams (потоки)
Представьте, что вам нужно прочитать файл размером 2 ГБ. Метод readFile загрузит его целиком в оперативную память, что может привести к утечке памяти. Для таких случаев существуют потоки (fs.createReadStream и fs.createWriteStream).
Поток обрабатывает данные частями (чанками), не храня весь файл в памяти.
Пример: копирование большого файла
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 });
console.log(`Папка "${dirPath}" создана или уже существует`);
} catch (err) {
if (err.code === 'EEXIST') {
console.log('Папка уже существует');
} else {
console.error('Ошибка:', err);
}
}
}
ensureDir('./uploads/images');
Опция { recursive: true } позволяет создавать вложенные папки (например, a/b/c), если родительские не существуют.
3.2 Чтение содержимого папки
const fs = require('fs').promises;
async function listFiles(dirPath) {
try {
const files = await fs.readdir(dirPath);
console.log('Содержимое папки:');
files.forEach(file => console.log(` - ${file}`));
} catch (err) {
console.error('Ошибка чтения папки:', err);
}
}
listFiles('./uploads');
3.3 Удаление папки с содержимым
const fs = require('fs').promises;
async function removeDir(dirPath) {
try {
await fs.rm(dirPath, { recursive: true, force: true });
console.log(`Папка "${dirPath}" удалена`);
} catch (err) {
console.error('Ошибка удаления:', err);
}
}
removeDir('./temp');
Метод rm (доступен с Node.js 14.14) — самый современный способ удалить папку со всем содержимым.
4. Практический пример: сборщик логов
Давайте объединим полученные знания в небольшой утилите, которая читает все .log файлы из папки, фильтрует ошибки и записывает их в отдельный файл.
const fs = require('fs').promises;
const path = require('path');
async function collectErrors(logsDir, outputFile) {
try {
const files = await fs.readdir(logsDir);
const errors = [];
for (const file of files) {
if (path.extname(file) !== '.log') continue;
const filePath = path.join(logsDir, file);
const content = await fs.readFile(filePath, 'utf-8');
const lines = content.split('\n');
lines.forEach((line, index) => {
if (line.toLowerCase().includes('error')) {
errors.push(`[${file}:${index + 1}] ${line}`);
}
});
}
if (errors.length > 0