Что такое Node.js на самом деле
Node.js — это JavaScript вне браузера: V8 исполняет код, libuv даёт однопоточный event loop с неблокирующим I/O, а встроенная библиотека открывает файлы, сеть и процесс. Силён в I/O, слаб в тяжёлых вычислениях.
Ты выучил JavaScript, чтобы кнопка на странице меняла цвет. Потом тебе дают server.js и говорят: «запусти через node». Ни браузера, ни <button>, ни document — а оно при этом обслуживает десять тысяч API-запросов в один поток и не плавится. Тот же язык, которым ты переключал CSS-класс, теперь читает файлы с диска и общается с базой данных. Как? Node.js — это не новый язык. Это runtime, который берёт уже знакомый тебе JavaScript и запускает его там, куда браузер его не пускал: на сервере.
Runtime, а не язык: V8 + libuv + стандартная библиотека
Прежде чем написать первую строку серверного кода, стоит разобраться, что такое Node.js на самом деле — потому что модель, которую ты выстроишь здесь, объяснит каждую странность позже: почему нет window, почему один медленный эндпоинт может заморозить весь API.
Когда говорят «Node.js», имеют в виду три вещи, свинченные в одну программу.
Первая — это V8, тот же движок, что Chrome использует для исполнения JavaScript. V8 берёт твой .js-исходник, компилирует его в машинный код и быстро запускает. Но сам по себе V8 только понимает JavaScript; он понятия не имеет, как открыть файл или принять сетевое соединение. В спецификации языка нет ни fs, ни http, ни сокетов.
Вторая часть закрывает эту дыру: libuv — C-библиотека, дающая Node event loop (цикл событий — механизм, который бесконечно вынимает завершённые задачи из очереди и запускает колбэки) плюс асинхронный I/O (ввод-вывод — диск, сеть, таймеры). Именно libuv позволяет одному потоку ждать тысячи соединений сразу, а не блокироваться на каждом. Ещё она держит в фоне небольшой пул потоков для тех немногих операций, что ОС не умеет делать асинхронно (часть работы с файловой системой и криптографией).
Третья — это стандартная библиотека Node: встроенные модули, открывающие всё это для JavaScript: fs для файлов, http/net для серверов и сокетов, path, process для самой работающей программы, crypto, stream и другие. Именно её ты на самом деле require или import.
// Никакого браузера — это работа стандартной библиотеки.
import { readFile } from "node:fs/promises";
import http from "node:http";
const html = await readFile("./index.html", "utf8"); // libuv читает диск
http.createServer((req, res) => res.end(html)) // V8 исполняет твой колбэк
.listen(3000);
console.log(`pid ${process.pid} слушает :3000`); // process = эта программаЗачем вообще нужна такая связка? До Node JavaScript был заперт в браузере, а серверы писали на PHP, Python, Ruby или Java. Node позволил командам писать на одном языке по всему стеку — и фронтенд, и бэкенд — и пользоваться огромной экосистемой пакетов npm (Node Package Manager — крупнейший реестр ПО в мире). Именно это решение объясняет большую часть того, почему Node захватил серверный тулинг.
Один поток, event loop и неблокирующий I/O
Вот идея, из-за которой Node кажется странным после других бэкендов: твой JavaScript исполняется в одном потоке. Никакого «поток на запрос». Как же один поток обслуживает тысячи клиентов?
Фокус в неблокирующем I/O. Когда твой код просит прочитать файл или сделать запрос к базе, Node не садится и не ждёт ответа. Он передаёт запрос в libuv (которая использует ОС или пул потоков), регистрирует колбэк и тут же переходит к другой работе. Когда данные готовы, libuv кладёт колбэк в очередь, и event loop — цикл, работающий бесконечно и вынимающий завершённую работу из очередей, чтобы исполнить колбэки, — его запускает. Один поток почти никогда не простаивает и почти никогда не заблокирован, поэтому он чередует тысячи операций «в полёте».
Есть и обратная сторона. Если дать этому единственному потоку CPU-bound работу — плотный цикл, хэширующий миллион паролей, ресайз огромной картинки, синхронный разбор гигантского JSON, — то некому принимать запросы, пока он молотит. Event loop заблокирован. Все остальные клиенты ждут. I/O разгружается; вычисления — нет.
Где Node силён, а где нет
Эта архитектура не лучше и не хуже бэкендов с «потоком на запрос» — она заточена под определённый род работы. Понимая эту заточку, ты знаешь, когда тянуться за Node.
| Аспект | JavaScript в браузере | JavaScript в Node.js |
|---|---|---|
| Где исполняется | Внутри веб-страницы, во вкладке браузера пользователя | Как отдельный процесс на сервере (или твоей машине) |
| Движок | V8 (Chrome) / SpiderMonkey / JSC | V8 + libuv (event loop, async I/O) |
| Платформенные API | DOM, fetch, localStorage, Web API | fs, net/http, crypto, child_process |
| Глобальный объект | window / document | global / process (env, argv, pid) |
| Нет доступа к | Локальной файловой системе, сырым сокетам | DOM, странице (страницы попросту нет) |
Node отлично справляется с I/O-bound работой, потому что именно под неё созданы неблокирующий I/O и event loop: HTTP-API и микросервисы, которые в основном ждут базы и другие сервисы, real-time-системы (чаты, живые дашборды) поверх WebSocket и инструменты разработчика (бандлеры, линтеры, CLI), где доминирует чтение и запись файлов. И он слаб в тяжёлых CPU-bound вычислениях — перекодировании видео, больших численных симуляциях, объёмной синхронной криптографии — потому что такая работа блокирует единственный поток. Запасные выходы реальны, но осознанны: worker_threads, чтобы вынести вычисления с главного потока, или просто язык, лучше подходящий для перемалывания чисел.
Экосистема — вторая половина картины. npm — это реестр пакетов и установщик Node, крупнейший реестр ПО в мире. Поверх него лежат фреймворки, с которыми ты встретишься дальше: Express и Fastify для HTTP-серверов, NestJS для структурированных, «с мнением» бэкендов. Относись к этим названиям как к карте, а не как к домашке на сегодня.
▸Почему это работает
«Однопоточность» — формулировка слегка неточная, и полезно понимать почему. Твой JavaScript исполняется в одном потоке, но Node как процесс — нет. libuv держит фоновый пул потоков (по умолчанию 4) для операций с файловой системой и криптографией, которые ОС не умеет делать асинхронно, а ты можешь поднять настоящие worker_threads для параллельных вычислений. Модель «один поток» описывает исполнение твоего кода и event loop — а не весь runtime. Практический вывод не меняется: не блокируй этот единственный JS-поток синхронными вычислениями.
Коллега говорит: «Node.js — это новый язык программирования для сервера». В чём точная поправка?
Один поток Node обслуживает тысячи HTTP-запросов, каждый из которых ходит в базу. Затем ты добавляешь эндпоинт, синхронно ресайзящий огромные картинки. Что произойдёт?
Ты решаешь, подходит ли Node.js под нагрузку. Что из этого — конёк Node, а что воюет с его моделью?
- 01Назови три части, из которых состоит Node.js, и что делает каждая.
- 02Node исполняет твой JS в одном потоке. Объясни, как он всё же обслуживает тысячи клиентов, и какой единственный род работы это ломает.
Node.js — это не новый язык, а runtime, который запускает уже знакомый тебе JavaScript вне браузера. Он свинчивает три вещи: V8, исполняющий твой JS; libuv, дающую event loop, асинхронный I/O и небольшой фоновый пул потоков; и стандартную библиотеку (fs, http, net, path, process, crypto), открывающую файловую систему, сеть и сам процесс твоему коду. Он появился потому, что позволил писать на одном языке по всему стеку и пользоваться огромной экосистемой npm. Его определяющая модель — единственный JavaScript-поток, управляемый event loop с неблокирующим I/O: медленное ожидание запроса разгружается в libuv, чтобы один поток оставался свободным и обслуживал тысячи других, запуская каждый колбэк, когда его данные готовы. Это делает Node отличным в I/O-bound API, real-time-системах и тулинге разработчика — и слабым в тяжёлых вычислениях, которые блокируют поток и требуют worker_threads или другого языка. В отличие от браузерного JS здесь нет DOM или window; вместо них — global, process и стандартная библиотека. Теперь, когда встретишь API, который необъяснимо тормозит под нагрузкой — даже если он «только читает из базы», — у тебя будет словарь, чтобы задать правильный вопрос: что-то блокирует event loop?
Практика
Начни сверху. Задачи идут от простого к сложному: вспомнить факт, применить к случаю, затем senior-уровень. Открой, попробуй, потом открой ответ.
Что-то непонятно?
Задай вопрос по этому уроку. Вопросы анонимны и попадают напрямую автору — урок станет лучше.
Примени это
Примени этот урок в реальном проекте.