Работа с файловой системой в TypeScript: практическое руководство

Онлайн-тренажер Python для начинающих

Изучайте Python легко и без перегрузки теорией. Решайте практические задачи с автоматической проверкой, получайте подсказки на русском языке и пишите код прямо в браузере — без необходимости что-либо устанавливать.

Начать курс

Введение: Зачем нужна работа с файловой системой в TypeScript?

Каждое приложение рано или поздно сталкивается с необходимостью взаимодействовать с файловой системой: сохранять конфигурации, обрабатывать загруженные файлы, вести логи или управлять проектами. TypeScript, будучи надстройкой над JavaScript, предоставляет разработчику мощные инструменты для работы с файлами и директориями через модуль fs (File System) из Node.js.

В этой статье мы на практических примерах разберём основные операции с файловой системой в TypeScript: чтение и запись файлов, создание и удаление директорий, а также обработку ошибок. Все примеры будут представлены с использованием строгой типизации, что является ключевым преимуществом TypeScript.

Основные модули для работы с файловой системой

Прежде чем перейти к практике, познакомимся с тремя основными модулями, которые понадобятся для работы:

  • fs — основной модуль для операций с файловой системой (чтение, запись, удаление)
  • fs/promises — версия модуля fs с поддержкой промисов (рекомендуется для современного кода)
  • path — утилита для работы с путями файлов и директорий

Для импорта модулей в TypeScript используйте следующий синтаксис:

import * as fs from 'fs';
import * as fsPromises from 'fs/promises';
import * as path from 'path';

Обратите внимание: для корректной работы с TypeScript убедитесь, что в вашем файле tsconfig.json установлены опции "moduleResolution": "node" и "esModuleInterop": true.

Чтение файлов: синхронный и асинхронный подходы

Синхронное чтение с типизацией

Синхронные методы блокируют выполнение кода до завершения операции. Используйте их только для небольших файлов или в начальной загрузке приложения:

import * as fs from 'fs';
import * as path from 'path';

function readConfigSync(): Record<string, unknown> {
  const configPath = path.join(__dirname, 'config.json');
  
  try {
    const data = fs.readFileSync(configPath, 'utf-8');
    return JSON.parse(data) as Record<string, unknown>;
  } catch (error) {
    if (error instanceof Error) {
      console.error('Ошибка чтения конфига:', error.message);
    }
    return {};
  }
}

const config = readConfigSync();
console.log('Конфигурация загружена:', config);

Асинхронное чтение с async/await (рекомендуемый способ)

Для production-кода всегда используйте асинхронные методы. TypeScript отлично работает с async/await:

import { readFile } from 'fs/promises';
import * as path from 'path';

interface UserData {
  id: number;
  name: string;
  email: string;
}

async function readUserData(fileName: string): Promise<UserData[]> {
  const filePath = path.resolve('data', fileName);
  
  try {
    const rawData = await readFile(filePath, { encoding: 'utf-8' });
    const users: UserData[] = JSON.parse(rawData);
    return users;
  } catch (error) {
    if (error instanceof Error) {
      throw new Error(`Не удалось прочитать файл ${fileName}: ${error.message}`);
    }
    throw error;
  }
}

// Использование
(async () => {
  try {
    const users = await readUserData('users.json');
    console.log(`Загружено ${users.length} пользователей`);
  } catch (error) {
    console.error(error);
  }
})();

Запись и обновление файлов

Создание и перезапись файлов

Для записи данных в файл используйте метод writeFile. Он создаст файл, если он не существует, или перезапишет существующий:

import { writeFile, mkdir } from 'fs/promises';
import * as path from 'path';

interface LogEntry {
  timestamp: string;
  level: 'info' | 'warn' | 'error';
  message: string;
}

async function saveLog(entry: LogEntry): Promise<void> {
  const logsDir = path.join(process.cwd(), 'logs');
  const logFile = path.join(logsDir, 'app.log');
  
  // Создаём директорию, если её нет
  await mkdir(logsDir, { recursive: true });
  
  const logLine = JSON.stringify(entry) + '\n';
  
  // append: true добавляет данные в конец файла
  await writeFile(logFile, logLine, { flag: 'a' });
  console.log('Лог сохранён');
}

// Пример использования
(async () => {
  await saveLog({
    timestamp: new Date().toISOString(),
    level: 'info',
    message: 'Приложение запущено'
  });
})();

Работа с JSON-файлами: типизированное чтение и запись

Создадим универсальную утилиту для работы с JSON-данными:

import { readFile, writeFile } from 'fs/promises';
import * as path from 'path';

class JsonFileManager<T> {
  private filePath: string;
  
  constructor(fileName: string, private defaultValue: T) {
    this.filePath = path.resolve('data', fileName);
  }
  
  async read(): Promise<T> {
    try {
      const data = await readFile(this.filePath, 'utf-8');
      return JSON.parse(data) as T;
    } catch {
      // Если файл не существует, возвращаем значение по умолчанию
      return this.defaultValue;
    }
  }
  
  async write(data: T): Promise<void> {
    const jsonString = JSON.stringify(data, null, 2);
    await writeFile(this.filePath, jsonString, 'utf-8');
  }
  
  async update(updater: (current: T) => T): Promise<void> {
    const currentData = await this.read();
    const updatedData = updater(currentData);
    await this.write(updatedData);
  }
}

// Использование
interface Settings {
  theme: 'light' | 'dark';
  fontSize: number;
}

const settingsManager = new JsonFileManager<Settings>('settings.json', {
  theme: 'light',
  fontSize: 14
});

(async () => {
  const settings = await settingsManager.read();
  console.log('Текущие настройки:', settings);
  
  await settingsManager.update(current => ({
    ...current,
    theme: 'dark'
  }));
})();

Работа с директориями и навигация

Проверка существования и создание папок

Часто необходимо убедиться, что директория существует, прежде чем работать с файлами внутри неё:

import { access, mkdir, readdir } from 'fs/promises';
import * as path from 'path';

async function ensureDirectory(dirPath: string): Promise<boolean> {
  try {
    await access(dirPath);
    return true; // директория существует                

Блоги

Рекомендаций по книгам