Бэкенд постоянно читает конфиги, пишет файлы, запускает внешние программы и корректно завершается по сигналу. Здесь собраны 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.