Node.js: JavaScript на сервере (2009)
В 2009 году стандартный способ написать веб-сервер выглядел так: Apache + PHP, или Tomcat + Java, или Nginx + Python/Ruby. Каждый запрос получал поток из пула. Поток блокировался на I/O - чтении файла, запросе к БД, ожидании сети. Пока поток ждал, он потреблял память (~1MB на поток в Java) ничего не делая.
Это работало. Но при тысячах одновременных соединений - проблема C10K: 10 000 одновременных клиентов требовали 10GB RAM только на потоки.
Райан Даль думал об этой проблеме два года. Его ответ: никаких потоков. Один поток. Event loop. Всё I/O - неблокирующее, через колбэки.
И язык для этого уже существовал. JavaScript из браузера - он и так был однопоточным с event loop. V8 2008 года сделал его достаточно быстрым. Оставалось добавить API для файловой системы, сети, и процессов.
Hello World: 10 строк, 10 000 соединений
// Именно это Даль показал на JSConf EU 2009
var http = require('http');
var server = http.createServer(function(req, res) {
res.writeHead(200, {'Content-Type': 'text/plain'});
res.end('Hello World\n');
});
server.listen(8000);
console.log('Server running at http://127.0.0.1:8000/');
// Это - полный веб-сервер.
// Не конфигурационный файл Apache.
// Не XML дескриптор деплоя Tomcat.
// 10 строк JavaScript.
// Тест производительности (Ubuntu, EC2 small, 2009):
// Apache (prefork): ~1000 req/sec при 1000 concurrent connections, 150MB RAM
// Node.js 0.1: ~4000 req/sec при 1000 concurrent connections, 15MB RAM
Почему event loop решает C10K
// Традиционный подход - блокирующий I/O:
// (псевдокод, показывающий проблему)
// Поток 1:
var data = fs.readFileSync('big-file.txt'); // СТОП. Поток заблокирован.
response.send(data); // Только после этого
// Поток 2 (параллельно, отдельный поток ОС):
var result = db.querySync('SELECT * FROM users'); // СТОП.
response.send(result);
// При 10000 соединениях: 10000 потоков, большинство - просто ждут.
// Node.js - неблокирующий:
fs.readFile('big-file.txt', function(err, data) {
// Колбэк вызовется когда файл прочитан.
// Пока файл читается - event loop обрабатывает ДРУГИЕ запросы.
response.end(data);
});
// Выполнение продолжается НЕМЕДЛЕННО.
// Один поток, но ничего не ждёт впустую.
// Наглядная аналогия, которую использовал Даль:
// Официант в ресторане.
// Плохой официант: принял заказ → стоит на кухне и ждёт → приносит еду.
// Хороший официант: принял заказ → передал на кухню → обслуживает других столиков →
// получил сигнал "готово" → приносит еду.
// Node.js - хороший официант.
Первый Express.js: маршрутизация (2009)
TJ Holowaychuk создал Express.js в конце 2009 года - Rails/Sinatra-подобный фреймворк для Node:
// Express 0.x - декабрь 2009
// npm install express (npm появился одновременно с Node 0.1)
var express = require('express');
var app = express.createServer();
// Маршруты:
app.get('/', function(req, res) {
res.send('Главная страница');
});
app.get('/users/:id', function(req, res) {
var userId = req.params.id;
// В 2009 - ещё без callback hell middleware
// Но уже с params - лучше чем парсить URL вручную
res.send('Пользователь ' + userId);
});
app.post('/api/data', function(req, res) {
// req.body - только если подключён bodyParser middleware
res.json({ status: 'ok', data: req.body });
});
app.listen(3000);
// Весь REST API - в одном файле, без XML, без конфигов.
// Для разработчика 2009 года, привыкшего к web.xml - магия.
npm: менеджер пакетов (тоже 2009)
// npm 0.0.1 - Айзек Шлютер, 2009
// Концепция: пакеты с зависимостями, локально в node_modules/
// package.json - структура которую мы используем сегодня:
{
"name": "my-app",
"version": "0.0.1",
"description": "Мой Node.js сервер",
"dependencies": {
"express": "0.9.0"
}
}
// Команды 2009 года:
// npm install express - установить в ./node_modules
// npm publish - опубликовать пакет
// npm search express - найти пакеты
// Принципиальное отличие от CPAN (Perl) и RubyGems:
// Зависимости локальны для проекта, не глобальны.
// Проект A и проект B могут иметь разные версии express.
// Это решило "dependency hell" который мучил Ruby/Python-разработчиков.
// К 2013 году npm стал крупнейшим реестром пакетов в мире.
// К 2023 - более 2 миллионов пакетов.
Callback-модель и её проблемы (видные уже в 2009)
// Паттерн 2009 года - вложенные колбэки
// Читать файл → парсить JSON → запрос к БД → ответ
var fs = require('fs');
fs.readFile('config.json', 'utf8', function(err, data) {
if (err) {
console.error('Ошибка чтения конфига:', err);
return;
}
var config;
try {
config = JSON.parse(data);
} catch(e) {
console.error('Невалидный JSON:', e);
return;
}
var db = require('mysql').createClient({
host: config.db.host,
user: config.db.user,
password: config.db.password
});
db.query('SELECT * FROM users WHERE active = 1', function(err, results) {
if (err) {
console.error('Ошибка БД:', err);
db.end();
return;
}
// Ответ после двух async операций
res.json({ users: results });
db.end();
});
});
// Это называли "callback hell" или "pyramid of doom".
// Решение появилось позже: Promises (2011), async/await (2017).
// В 2009 году это был единственный способ - и большинство
// программистов считали это нормальным.
Контекст: почему именно сейчас
Node.js не мог существовать в 2005 году. V8 (2008) дал производительность. Но важен и контекст:
Ajax-приложения 2005-2008 создали задачи, которые плохо ложились на традиционные серверы: длинные HTTP-соединения (Comet), real-time обновления, сотни параллельных WebSocket-соединений. PHP/Apache был рождён для request-response, не для persistent connections.
JSON как стандарт - к 2009 году сервер возвращал JSON, браузер потреблял JSON. JavaScript на клиенте и сервере означал: один формат данных, нет конвертации, нет несоответствий типов.
V8 дал скорость. Без V8 Node.js на SpiderMonkey 2007 года был бы нежизнеспособен.
Всё это сошлось 8 ноября 2009 года в Берлине.
Что Node изменил в следующие два года
Node.js не стал заменой PHP или Java для монолитных приложений. Он нашёл нишу:
- API-серверы с высоким concurrency и низкой вычислительной нагрузкой
- Real-time: чаты, игры, коллаборативные инструменты
- Streaming: обработка больших файлов без загрузки в память
- Microservices (термин появился позже, паттерн - уже тогда)
К 2011 году Node.js использовали LinkedIn (мобильный бэкенд), Yahoo!, и сотни стартапов. Ryan Dahl ушёл из проекта в 2012-м - к тому моменту Node жил без него.
Самое важное, что сделал Node: доказал что JavaScript - серьёзный язык для серверной разработки. Не «скриптовый язык для браузера», а язык. Это изменило то, как индустрия думала о JavaScript навсегда.