Урок по треку Node.js и бэкенд 6 мин

Файлы, пути и процессы в Node.js

fs с промисами, пути, child_process, переменные окружения, сигналы и коды выхода.

Бэкенд постоянно читает конфиги, пишет файлы, запускает внешние программы и корректно завершается по сигналу. Здесь собраны API, о которых спрашивают на собесе.

Модуль fs: три стиля

import { readFile } from 'node:fs/promises';   // промисы: основной стиль
import { readFileSync } from 'node:fs';         // синхронный
import { readFile as readCb } from 'node:fs';   // колбэки: исторический

Синхронные функции блокируют event loop. Их можно использовать при старте приложения (прочитать конфиг) и в CLI-скриптах, но не в обработчиках запросов. Асинхронные функции fs выполняются в пуле потоков libuv.

Для больших файлов используй потоки (createReadStream), а не readFile: тот грузит всё в память и для файла больше 2 ГБ бросает ERR_FS_FILE_TOO_LARGE.

Полезное:

  • fs.mkdir(dir, { recursive: true }) не падает, если папка уже есть.
  • fs.rm(path, { recursive: true, force: true }) удаляет как rm -rf.
  • Проверять existsSync перед открытием это гонка: файл могут удалить между проверкой и открытием. Надёжнее сразу открыть и обработать ошибку ENOENT.
  • Атомарная запись: пишем во временный файл и делаем rename поверх старого. Читатель никогда не увидит полузаписанный файл.

Пути

path.join('/a', '/b');      // '/a/b'  склеивает части
path.resolve('/a', '/b');   // '/b'    строит абсолютный путь справа налево
path.join('a', '../b');     // 'b'     нормализует ..
path.extname('x.tar.gz');   // '.gz'

Относительные пути в fs считаются от process.cwd(), то есть от папки, откуда запустили процесс, а не от папки файла. Путь к ресурсу рядом с модулем строят от __dirname (CommonJS) или import.meta.dirname (ESM).

Процесс

  • process.argv: [путь к node, путь к скрипту, ...аргументы].
  • process.env: переменные окружения. Все значения строки: process.env.PORT = 3000 сохранит '3000', а undefined превратится в строку 'undefined'. Числа и булевы значения парсят явно и валидируют при старте.
  • В Node 20.6+ есть встроенная загрузка .env: node --env-file=.env app.js.
  • Код выхода: 0 успех, не 0 ошибка. Необработанное исключение или отклонённый промис дают код 1.
  • process.exit(code) завершает немедленно, не дожидаясь записи в потоки и незавершённых операций. Мягче выставить process.exitCode = 1 и дать циклу опустеть.

Сигналы и graceful shutdown

Ctrl+C посылает SIGINT, docker stop и Kubernetes посылают SIGTERM, а если процесс не завершился за отведённое время (в Docker 10 с), то SIGKILL. SIGKILL перехватить нельзя.

process.on('SIGTERM', async () => {
  server.close();          // перестать принимать новые соединения
  await db.end();          // закрыть пулы
  process.exit(0);
});

Дочерние процессы

ФункцияЧто делает
spawn(cmd, args)запускает программу, вывод идёт потоком
exec(cmdline)запускает через shell, вывод буферизуется целиком (лимит maxBuffer 1 МБ)
execFile(file, args)как exec, но без shell
fork(module)запускает Node-скрипт с IPC-каналом: send() и on('message')

exec с пользовательским вводом в строке команды это command injection: exec('convert ' + name) при имени a.png; rm -rf ~ выполнит обе команды. Передавай аргументы массивом в execFile или spawn.

Частые ошибки

  • readFileSync в обработчике запроса.
  • Относительные пути, которые ломаются при запуске из другой папки.
  • Сравнение process.env.DEBUG === true: это всегда false, там строка.
  • process.exit() сразу после console.log в пайп: вывод может обрезаться.
  • exec с конкатенацией пользовательских данных.

Что спросят на собесе

  • Чем spawn отличается от exec и fork.
  • Как корректно завершить сервер по SIGTERM.
  • Чем path.join отличается от path.resolve.
  • Почему синхронные функции fs опасны в сервере.
  • Какого типа значения в process.env.

Закрепите за 5 минут

После урока в тренажёре 3-5 проверочных вопросов, дальше модуль уходит в повторения.