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.json script "info": "node info.js" и запусти через npm run info
  • Загляни в package-lock.json - там точные версии всех транзитивных зависимостей dotenv

Зарегистрируйтесь бесплатно, чтобы пройти квиз, решить задание с автопроверкой и вести прогресс.