← назад к разделу

Конфигурация и файлы выглядят простыми до первого production-инцидента: секрет попал в лог, запись не дождалась завершения, а дескрипторы закончились через час работы. Полезно завести строгие правила на уровне приложения.

Окружение процесса

process.env хранит строки, а не булевы значения и числа. Проверяйте и преобразуйте конфигурацию на старте, чтобы процесс упал с понятной ошибкой до открытия порта.

function positiveInt(name: string): number {
  const raw = process.env[name];
  const value = Number(raw);
  if (!Number.isSafeInteger(value) || value <= 0) {
    throw new Error(`${name} must be a positive integer`);
  }
  return value;
}

const port = positiveInt('PORT');

Не печатайте объект окружения целиком: там могут быть токены. process.cwd() зависит от каталога запуска, поэтому пути приложения строят от явного корня или от import.meta.url, а не от текущего каталога пользователя.

Асинхронные файлы

fs.promises не делает диск мгновенным и не запускает JavaScript в другом потоке. Операции используют механизмы libuv, а результат возвращается в основной цикл.

import { readFile } from 'node:fs/promises';

const raw = await readFile(new URL('./orders.json', import.meta.url), 'utf8');
const orders = JSON.parse(raw);

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

Дескриптор закрывают всегда

FileHandle - ресурс ОС, а не просто объект JavaScript. Закрытие должно быть в finally, включая ошибки чтения и записи.

import { open } from 'node:fs/promises';

const file = await open('/var/lib/shop/outbox.ndjson', 'a');
try {
  await file.appendFile(`${JSON.stringify({ type: 'OrderCreated', id: 42 })}\n`, 'utf8');
  await file.sync();
} finally {
  await file.close();
}

appendFile подтверждает запись в буфер ОС, а sync просит сбросить данные на устройство. Для outbox и журналов, после которых нельзя потерять подтвержденное событие, это разные уровни надежности.

Коротко

  • В process.env все строки; числа и флаги валидируют при старте.
  • Пути не зависят от случайного каталога запуска.
  • Маленькие файлы читают целиком, большие - потоково.
  • FileHandle закрывают в finally.
  • appendFile и sync дают разные гарантии записи.

Что почитать дальше