Node.js: JS вне браузера
В предыдущих трёх уроках мы работали в браузере - DOM, события, fetch на сервер. Сейчас уберём браузер и оставим только JS как движок. Это и есть Node.js: тот же синтаксис, типы, async/await, fetch - но без document, без DOM, без событий клика. Зато с доступом к файловой системе, процессам, переменным окружения.
Зачем бэкендеру Node
Backend ты пишешь на Go или PHP. Node для тебя - второй runtime для одноразовых задач и обвязки:
- Скрипт автоматизации: прочитать JSON-файл, пройтись по записям, сходить в API, сгенерировать отчёт.
- Утилиты сборки контента: в BackendStart
content/courses.jsonвалидируется npm-скриптом, миграции иногда удобнее накатить через JS-скрипт, чем через psql. - Дебаг интеграции с фронтом: воспроизвести запрос фронта руками, посмотреть что приходит на backend.
- Полноценный backend (Express/Fastify/NestJS) - альтернатива Go/PHP, когда команда на JS. Не наш кейс, но знать как это устроено - полезно.
Когда Node лучше Go для одноразовой задачи: JSON и сетевые запросы пишутся короче, экосистема npm закрывает 90% потребностей сторонними пакетами. Когда хуже: всё, что требует сильной типизации, конкурентности, или интенсивных вычислений.
Установка
# macOS - через nvm (Node Version Manager), это де-факто стандарт
curl -o- https://raw.githubusercontent.com/nvm-sh/nvm/v0.39.7/install.sh | bash
nvm install 20 # LTS-версия
nvm use 20
# Linux - пакетный менеджер не подходит (там старая версия). Тот же nvm.
# Windows - через nvm-windows или официальный установщик.
# Проверка
node --version # v20.x.x
npm --version # 10.x.x
nvm - потому что разные проекты часто требуют разные версии Node. С системным Node ты упрёшься в это через неделю.
REPL и скрипты
$ node
> 1 + 1
2
> const x = [1, 2, 3]
undefined
> x.map(n => n * 2)
[ 2, 4, 6 ]
> .exit
REPL (Read-Eval-Print Loop) - интерактивная консоль. Удобно потыкать одноразовые куски кода.
Файл-скрипт:
$ echo "console.log('Привет')" > hello.js
$ node hello.js
Привет
Любой .js файл запускается через node <файл>.
package.json - паспорт проекта
mkdir my-tool
cd my-tool
npm init -y # создаст package.json с дефолтами
{
"name": "my-tool",
"version": "1.0.0",
"type": "module",
"scripts": {
"start": "node index.js",
"validate": "node scripts/validate.js"
},
"dependencies": {
"axios": "^1.6.0"
},
"devDependencies": {
"eslint": "^9.0.0"
}
}
Что внутри:
name,version- имя пакета и версия. Если не публикуешь в npm - формально, но требуется.type: "module"- использоватьimport/export(ES Modules). Без этого Node работает в режиме CommonJS сrequire/module.exports- старая система, в новом коде не пиши.scripts- алиасы команд.npm run validateзапуститnode scripts/validate.js. Стандартные имена (start,test,build) можно безrun:npm start.dependencies- runtime-зависимости. То, без чего код не работает (axios для HTTP, dotenv для env, etc.).devDependencies- только для разработки (тесты, линтеры, форматеры). В production не ставятся, если запускатьnpm install --production.
npm install и node_modules
npm install axios # установит и запишет в dependencies
npm install --save-dev eslint # запишет в devDependencies
npm install # установит всё из package.json
После установки появляется:
node_modules/- все пакеты лежат тут. Размер легко уходит в сотни МБ. В git не коммитят (.gitignore).package-lock.json- точные версии каждой зависимости. Коммитят - обеспечивает воспроизводимость билда.
Что есть в npm
Около 2 миллионов пакетов. Полезные для бэкендера:
axios - удобнее fetch для серьёзных задач (interceptors, timeout, retry)
node-fetch - fetch для старых Node (с Node 18+ встроен)
zod - валидация данных через схемы
dotenv - читать .env файлы
yargs / commander - парсинг аргументов командной строки
date-fns - операции с датами без боли
csv-parse - парсинг CSV
Различия с браузерным JS
Что есть в Node, нет в браузере:
process-process.argv,process.env,process.exit().fs(file system) -readFile,writeFile,readdir.path- кросс-платформенные операции с путями.os- информация о системе.__dirname,__filename- путь до текущего файла (только в CommonJS; в ES Modules -import.meta.url).require(CommonJS) илиimport(ESM).
Что есть в браузере, нет в Node:
document,window, DOM.localStorage,sessionStorage.- Любые UI-API.
Что есть в обоих:
- Все типы, синтаксис, async/await, Promise.
console.log,setTimeout,setInterval.fetch(с Node 18+).URL,URLSearchParams.
Простой скрипт автоматизации
Задача: пройти по JSON-файлу с пользователями, для каждого активного дёрнуть API.
// scripts/notify-active.js
import { readFile } from 'node:fs/promises';
const inputPath = process.argv[2]; // первый аргумент после имени скрипта
if (!inputPath) {
console.error('Usage: node notify-active.js <path-to-users.json>');
process.exit(1);
}
const users = JSON.parse(await readFile(inputPath, 'utf-8'));
const active = users.filter(u => u.status === 'active');
console.log(`Найдено ${active.length} активных из ${users.length}`);
for (const user of active) {
try {
const response = await fetch('https://api.example.com/notify', {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({ userId: user.id }),
});
if (!response.ok) {
console.error(`User ${user.id}: HTTP ${response.status}`);
continue;
}
console.log(`User ${user.id}: notified`);
} catch (e) {
console.error(`User ${user.id}: network error`, e.message);
}
}
Запуск:
node scripts/notify-active.js users.json
Это полноценный одноразовый скрипт: читает файл, парсит JSON, фильтрует, для каждого делает HTTP-запрос с обработкой ошибок. На Go ты бы написал то же самое, но в три раза дольше.
Переменные окружения
// process.env - объект с env-переменными процесса
const dbUrl = process.env.DATABASE_URL;
if (!dbUrl) {
console.error('DATABASE_URL не задан');
process.exit(1);
}
Запуск:
DATABASE_URL=postgres://... node script.js
Или через .env файл + dotenv:
npm install dotenv
import 'dotenv/config';
console.log(process.env.DATABASE_URL); // прочитал из .env
В BackendStart .env, .env.deploy, .env.production - то же самое: переменные окружения. Frontend через Vite их вшивает в bundle на сборке, Node читает в runtime через process.env.
Что пропустили намеренно
- Streams и backpressure - для работы с большими файлами по кусочкам. Нишево.
- Worker threads, child_process - параллельность в Node. Сложная и редкая тема.
- HTTP-сервер на чистом
http- обычно используют Express/Fastify, голый модуль не пишут. - Тестирование (Jest/Vitest/Node test runner) - отдельная тема.
- TypeScript - упомянем ниже как направление, но не разворачиваем.
Куда дальше
JS большой, мы дали минимум для бэкендера. Если зайдёт - три направления:
- TypeScript - типы поверх JS. Полезно даже для скриптов автоматизации; обязательно - если когда-нибудь будешь писать Node-backend. Базовый порог низкий (учится за выходные), потолок высокий (generics, type-level programming - на любителя).
- Node как backend (Express, Fastify, NestJS) - тот же Node, но HTTP-сервер. Альтернатива Go/PHP, когда команда на JS. Express - простой и старый; Fastify - быстрее и современнее; NestJS - Angular-style с декораторами и DI.
- React / Vue / Svelte - фреймворки SPA, отдельная профессия (frontend). BackendStart этому не учит - но дорогу до старта мы расчистили: ты знаешь JS, DOM, fetch, асинхронность. С этой базы туториал React читается без шока.
jQuery встретится в legacy-коде - читать научишься без обучения. Новые проекты на нём не пишут, браузер умеет всё то же без библиотек.
Что после JS-блока
Дальше в треке web:
- URL, encoding и безопасность - закроем тему XSS, на которую ссылались в js-dom (innerHTML), плюс CSRF и кодирование URL.
- Cookie, session, localStorage - где хранить данные пользователя в браузере. Используем
localStorage.setItem/getItem, которые теперь не магия. - HTTP-кэширование - Cache-Control, ETag. Влияет на то, насколько быстро твой backend ощущается фронту.
- Мини-проект - собрать всё вместе: лендинг с формой, JS-валидацией, fetch в backend, деплой.
Мини-задание
- Установи Node 20 через nvm
- Создай новую папку, выполни
npm init -y, добавь"type": "module" - Установи
dotenv(npm install dotenv), создай.envс одной переменной, прочитай её из скрипта - Напиши скрипт, который читает JSON-файл (любой - например,
package.json) и выводит количество ключей в корне - Добавь в
package.jsonscript"info": "node info.js"и запусти черезnpm run info - Загляни в
package-lock.json- там точные версии всех транзитивных зависимостей dotenv