TypeScript и файлы: от новичка до профи за 15 минут

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

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

Начать курс

Почему работа с файлами в TypeScript — это не страшно, а удобно

Когда я только начинал писать на TypeScript, меня пугала мысль: «А как же файлы? В браузере-то всё просто, а тут — сервер, диски, пути...». Но, как оказалось, работа с файлами и файловой системой в TypeScript — это как собрать конструктор LEGO: есть чёткие детали (модули fs и path), есть инструкция (типизация), и результат получается предсказуемым. Никаких сюрпризов, только чистый код.

В этой статье мы разберёмся, как читать, писать, переименовывать и удалять файлы, используя современный TypeScript для Node.js. Вы узнаете, почему стоит использовать fs/promises вместо старого доброго fs, как не запутаться в путях и как красиво обрабатывать ошибки. Поехали!

Подготовка: что нужно знать перед стартом

Прежде чем нырять в код, убедитесь, что у вас установлен Node.js (версия 14+ — там уже есть нативные промисы). Также создайте простой проект:

mkdir ts-files-demo
cd ts-files-demo
npm init -y
npm install typescript @types/node --save-dev
npx tsc --init

В файле tsconfig.json обязательно включите "module": "NodeNext" или "ESNext" и "target": "ES2022". Теперь вы готовы к работе с файловой системой TypeScript.

Импорты, которые пригодятся всегда

Самый правильный способ импортировать модули для работы с файлами сегодня — это:

import { readFile, writeFile, unlink, mkdir, access } from 'fs/promises';
import { join, dirname, basename, extname } from 'path';
import { fileURLToPath } from 'url';

Обратите внимание: мы не используем fs с колбэками. Только fs/promises — это современный стандарт, который отлично сочетается с async/await и типизацией TypeScript.

Чтение файла: от простого к безопасному

Начнём с классики — прочитаем текстовый файл. Допустим, у нас есть data.txt с каким-то содержимым.

import { readFile } from 'fs/promises';

async function readTextFile(filePath: string): Promise<string> {
  try {
    const content = await readFile(filePath, 'utf-8');
    return content;
  } catch (error) {
    console.error('Ошибка при чтении файла:', error);
    throw error;
  }
}

// Использование
const data = await readTextFile('./data.txt');
console.log(data);

Просто, правда? Но в реальных проектах мы часто работаем с JSON. Давайте сделаем универсальную функцию с дженериком:

import { readFile } from 'fs/promises';

async function readJsonFile<T>(filePath: string): Promise<T> {
  const raw = await readFile(filePath, 'utf-8');
  return JSON.parse(raw) as T;
}

// Пример: читаем конфиг
interface Config {
  port: number;
  dbUrl: string;
}

const config = await readJsonFile<Config>('./config.json');
console.log(config.port); // тип number — TypeScript сам подскажет

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

Запись в файл: создаём и обновляем данные

Теперь научимся сохранять информацию. Функция writeFile перезаписывает файл, если он существует, или создаёт новый.

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

async function saveToFile(filePath: string, data: string): Promise<void> {
  // Сначала убедимся, что папка существует
  const dir = dirname(filePath);
  await mkdir(dir, { recursive: true });

  // Теперь пишем
  await writeFile(filePath, data, 'utf-8');
  console.log(`Файл ${filePath} успешно сохранён`);
}

// Сохраняем объект как JSON
const user = { name: 'Анна', age: 28, role: 'admin' };
await saveToFile('./output/user.json', JSON.stringify(user, null, 2));

Обратите внимание на { recursive: true } в mkdir — это создаст все вложенные папки, если их нет. Без этой опции пришлось бы создавать каждую папку вручную. Работа с путями TypeScript становится намного проще, когда используешь path и fs/promises вместе.

Дописывание в конец файла

Иногда нужно не перезаписать файл, а добавить строку в конец. Для этого есть appendFile:

import { appendFile } from 'fs/promises';

async function logMessage(message: string): Promise<void> {
  const timestamp = new Date().toISOString();
  await appendFile('./logs/app.log', `[${timestamp}] ${message}\n`, 'utf-8');
}

await logMessage('Сервер запущен');
await logMessage('Пользователь залогинился');

Идеально для логов, трекинга событий или записи результатов в реальном времени.

Удаление и перемещение файлов

Рано или поздно вам понадобится что-то удалить. Используем unlink для файлов и rmdir (или rm с опцией recursive) для папок.

import { unlink, rm } from 'fs/promises';

// Удаление одного файла
async function deleteFile(filePath: string): Promise<void> {
  try {
    await unlink(filePath);
    console.log(`Файл ${filePath} удалён`);
  } catch (error) {
    if ((error as NodeJS.ErrnoException).code === 'ENOENT') {
      console.warn('Файл не найден, пропускаем');
    } else {
      throw error;
    }
  }
}

// Удаление папки со всем содержимым
async function deleteFolder(folderPath: string): Promise<void> {
  await rm(folderPath, { recursive: true, force: true });
  console.log(`Папка ${folderPath} удалена`);
}

Кстати, обработка ошибок файлов TypeScript — это отдельное искусство. Обратите внимание на проверку кода ENOENT (Error NO ENTry — файл не существует). Всегда проверяйте такие коды, чтобы не падать с неожиданными исключениями.

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

До того как читать или писать, хорошо бы проверить, существует ли файл. Раньше использовали fs.exists, но он deprecated. Теперь правильный способ — access:

import { access, constants } from 'fs/promises';

async function fileExists(filePath: string): Promise<boolean> {
  try {
    await access(filePath, constants.F_OK);
    return true;
  } catch {
    return false;                

Блоги

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