Файловая система
Модуль fs
Работа с файлами и папками.
import fs from 'node:fs';
import fs from 'node:fs/promises'; // промисы
Синхронный vs асинхронный
Синхронный (*Sync):
const data = fs.readFileSync('file.txt', 'utf8');
console.log(data);
Блокирует поток. Для CLI-скриптов — ок. Для сервера — плохо.
Асинхронный (callback):
fs.readFile('file.txt', 'utf8', (err, data) => {
if (err) {
console.error(err);
return;
}
console.log(data);
});
Асинхронный (промисы) — современный:
import fs from 'node:fs/promises';
const data = await fs.readFile('file.txt', 'utf8');
console.log(data);
Правило: всегда используй fs/promises.
Чтение файла
import fs from 'node:fs/promises';
// Текст
const text = await fs.readFile('file.txt', 'utf8');
// Бинарные данные (без 'utf8')
const buffer = await fs.readFile('image.png');
Запись файла
await fs.writeFile('output.txt', 'Привет, мир!');
await fs.writeFile('data.json', JSON.stringify(data, null, 2));
Перезаписывает. Если не существует — создаст.
Добавление в файл
await fs.appendFile('log.txt', 'Новая строка\n');
Проверка существования
try {
await fs.access('file.txt');
console.log('Файл есть');
} catch {
console.log('Файла нет');
}
Не используй existsSync для критичных проверок — между проверкой и использованием файл может исчезнуть.
Лучше — try/catch вокруг операции.
Информация о файле
const stats = await fs.stat('file.txt');
console.log(stats.size); // размер в байтах
console.log(stats.isFile()); // файл?
console.log(stats.isDirectory());// папка?
console.log(stats.mtime); // дата изменения
Удаление
await fs.unlink('file.txt'); // удалить файл
await fs.rmdir('empty-folder'); // удалить пустую папку
await fs.rm('folder', { recursive: true }); // с содержимым
Переименование / перемещение
await fs.rename('old.txt', 'new.txt');
await fs.rename('folder/file.txt', 'other/file.txt');
Копирование
await fs.copyFile('source.txt', 'dest.txt');
Папки
Создать:
await fs.mkdir('new-folder');
await fs.mkdir('a/b/c', { recursive: true });
Прочитать:
const files = await fs.readdir('folder');
// ['file1.txt', 'file2.txt', 'subfolder']
const entries = await fs.readdir('folder', { withFileTypes: true });
entries.forEach(e => {
if (e.isDirectory()) console.log('DIR:', e.name);
else console.log('FILE:', e.name);
});
Модуль path
Работа с путями — кроссплатформенно.
import path from 'node:path';
Соединение:
path.join('src', 'utils', 'math.js');
// 'src/utils/math.js' (на Linux)
// 'src\\utils\\math.js' (на Windows)
Абсолютный путь:
path.resolve('src', 'index.js');
// '/home/user/project/src/index.js'
Имя и расширение:
path.basename('/a/b/file.txt'); // 'file.txt'
path.basename('/a/b/file.txt', '.txt'); // 'file'
path.extname('/a/b/file.txt'); // '.txt'
path.dirname('/a/b/file.txt'); // '/a/b'
Разбор:
path.parse('/a/b/file.txt');
// { root: '/', dir: '/a/b', base: 'file.txt', ext: '.txt', name: 'file' }
__dirname и __filename
CJS:
console.log(__dirname); // /home/user/project/src
console.log(__filename); // /home/user/project/src/index.js
ESM — нет. Аналоги:
import { fileURLToPath } from 'node:url';
import path from 'node:path';
const __filename = fileURLToPath(import.meta.url);
const __dirname = path.dirname(__filename);
Или (Node.js 20.11+):
console.log(import.meta.dirname);
console.log(import.meta.filename);
Абсолютные пути
Всегда используй:
import path from 'node:path';
const filePath = path.join(process.cwd(), 'data', 'file.json');
process.cwd() — текущая рабочая директория.
Не используй относительные — сломается при запуске из другой папки.
Рекурсивный обход
Node.js 20+:
for await (const entry of fs.glob('**/*.js')) {
console.log(entry);
}
Или вручную:
async function walk(dir) {
const entries = await fs.readdir(dir, { withFileTypes: true });
for (const entry of entries) {
const full = path.join(dir, entry.name);
if (entry.isDirectory()) {
await walk(full);
} else {
console.log(full);
}
}
}
Отслеживание изменений
import fs from 'node:fs';
fs.watch('file.txt', (eventType, filename) => {
console.log(eventType, filename);
});
eventType — 'rename' или 'change'.
Для разработки — chokidar (стабильнее).
Работа с JSON
import fs from 'node:fs/promises';
async function readJson(path) {
const text = await fs.readFile(path, 'utf8');
return JSON.parse(text);
}
async function writeJson(path, data) {
await fs.writeFile(path, JSON.stringify(data, null, 2));
}
Практический пример: логгер
import fs from 'node:fs/promises';
import path from 'node:path';
class Logger {
constructor(fileName = 'app.log') {
this.path = path.join(process.cwd(), 'logs', fileName);
}
async init() {
await fs.mkdir(path.dirname(this.path), { recursive: true });
}
async log(message) {
const line = `[${new Date().toISOString()}] ${message}\n`;
await fs.appendFile(this.path, line);
}
}
const logger = new Logger();
await logger.init();
await logger.log('Приложение запущено');
await logger.log('Пользователь вошёл');
Практика: чтение конфига
import fs from 'node:fs/promises';
import path from 'node:path';
async function loadConfig(name = 'config.json') {
const configPath = path.join(process.cwd(), name);
try {
const text = await fs.readFile(configPath, 'utf8');
return JSON.parse(text);
} catch (err) {
if (err.code === 'ENOENT') {
throw new Error(`Файл не найден: ${configPath}`);
}
throw err;
}
}
const config = await loadConfig();
console.log(config);
Коды ошибок
ENOENT— файл не найден.EACCES— нет прав.EEXIST— файл существует.ENOTDIR— не директория.EISDIR— это директория.
try {
await fs.readFile('missing.txt');
} catch (err) {
if (err.code === 'ENOENT') {
console.log('Файл не найден');
}
}
Итог
fs/promises— всегда.readFile,writeFile,appendFile— основные.readdir,mkdir,rm— для папок.path.join,path.resolve— пути.process.cwd()— рабочая папка.import.meta.dirname— папка модуля (ESM).- Коды ошибок —
ENOENT,EACCES. fs.watch— слежение.
Дальше — асинхронность и Event Loop.