Веб-разработка
Node.js и Express
В 2009 году Ryan Dahl представил Node.js с провокационным тезисом: Apache держит поток на каждое соединение - это расточительство. При 10 000 одновременных соединений Apache запускает 10 000 потоков. Node.js использует один поток с event loop. LinkedIn перешел с Rails на Node.js и сократил количество серверов с 30 до 3 при той же нагрузке. Неблокирующий I/O изменил представления о производительности веб-серверов.
- **Netflix:** перешел на Node.js для серверного рендеринга; время загрузки страницы снизилось на 70%, использование памяти - на 40%
- **PayPal:** переписал Java-сервис на Node.js; в два раза больше запросов в секунду, на 35% быстрее время ответа, команда пишет в два раза больше кода
- **Express vs Fastify:** Express занимает около 70% рынка Node.js серверов; Fastify в 2-3 раза быстрее Express за счет оптимизированной маршрутизации и JSON сериализации
Event Loop и асинхронность
Node.js обрабатывает тысячи одновременных соединений в одном потоке. Это звучит как ограничение, но на практике - архитектурное преимущество для I/O-intensive задач. Секрет: Event Loop + неблокирующий I/O. Пока Node.js ждет ответа от базы данных, он обрабатывает другие запросы. Единственный смертный грех - CPU-intensive операции в основном потоке: они блокируют весь event loop.
Event Loop фазы: timers (setTimeout/setInterval) -> pending callbacks -> idle/prepare -> poll (I/O) -> check (setImmediate) -> close callbacks. Microtask queue (Promise.then, queueMicrotask) выполняется между каждой фазой. process.nextTick - перед microtask queue. libuv - библиотека под капотом, управляет thread pool для file I/O, crypto, DNS.
Node.js сервер начинает отвечать с задержкой 2-3 секунды на все запросы, хотя CPU загружен на 100%. База данных работает нормально. Наиболее вероятная причина?
Express Middleware Pipeline
Express middleware - это конвейер функций, каждая из которых получает request, response и next. Порядок имеет значение: middleware выполняются в том порядке, в котором они зарегистрированы. Это незакономерное но критичное свойство: если поставить auth middleware после route handler - он никогда не выполнится для этого роута. Паттерн middleware позволяет строить приложение из переиспользуемых, тестируемых блоков.
Типы middleware: Application-level (app.use), Router-level (router.use), Error-handling (4 параметра: err, req, res, next), Built-in (express.json, express.static), Third-party (cors, helmet, morgan). next() без аргументов - следующий middleware. next(err) - переход к error handler. next('route') - пропуск оставшихся middleware для текущего route.
Express middleware вызывает next(new Error('DB timeout')). Что произойдет?
Express Router и структура приложения
Express Router - это мини-приложение: отдельный namespace для middleware и роутов. Вместо одного файла с тысячей строк, роутеры позволяют организовать код по доменам. Правильная структура роутинга - это не просто эстетика: это тестируемость (роутер можно тестировать отдельно), переиспользование (один роутер на несколько версий API) и безопасность (middleware применяются строго к нужным путям).
Express Router: router.get, router.post, router.use, router.param. app.use('/api/v1', router) - монтирование. router.route('/users').get(handler).post(handler) - цепочка методов для одного пути. express.Router({ strict: true }) - строгий режим (различает /users и /users/). mergeParams: true - доступ к params родительского роутера.
Роут app.get('/users', handler1) и app.get('/users', handler2) зарегистрированы один за другим. handler1 вызывает res.json(). Что произойдет с handler2?
Error Handling в Express
Необработанная ошибка в production - это 500 с трейсом стека в ответе клиенту. Централизованный error handler в Express - единственное место, где решается как отвечать на ошибки. Правильная стратегия: классифицировать ошибки (operational vs programming), логировать с контекстом, отвечать клиенту безопасно (без internal details). Async ошибки в Express 4 требуют обёртки или try/catch + next(err); Express 5 уже ловит их автоматически.
Express error handler: 4 параметра (err, req, res, next) - обязательно четыре. Operational errors (4xx) vs Programming errors (5xx): первые ожидаемые (not found, validation), вторые - баги. process.on('unhandledRejection') и process.on('uncaughtException') - последний рубеж, обычно restart процесса. async-express wrapper или express-async-errors - для удобной обработки async ошибок в Express 4.
Node.js не подходит для CPU-intensive задач, поэтому его нельзя использовать для обработки изображений или видео
Node.js не подходит для CPU-intensive задач в основном потоке; Worker Threads и child_process решают эту проблему
Node.js Worker Threads (с версии 10.5) позволяют запускать CPU-intensive код в отдельных потоках, не блокируя event loop. Sharp (обработка изображений) и ffmpeg-wrapper используют этот подход. Проблема не в Node.js, а в неправильном использовании архитектуры.
Express error handler зарегистрирован первым, до всех роутов. Он будет работать?
Ключевые идеи
- **Event Loop** - один поток, неблокирующий I/O; CPU-intensive операции в основном потоке блокируют все запросы; Worker Threads - решение
- **Middleware pipeline** - порядок регистрации критичен; next() продолжает цепочку, next(err) переходит к error handler, res.send() завершает
- **Router** - мини-приложение для организации кода по доменам; позволяет применять middleware точечно
- **Централизованный error handler** - последний middleware с 4 параметрами; отделяет operational ошибки от programming errors
Связанные темы
Node.js и Express - фундамент для более специализированных фреймворков:
- REST API Design — Express предоставляет инструменты для реализации REST; дизайн API определяет структуру роутинга
- NestJS — NestJS построен поверх Express/Fastify и добавляет DI, декораторы и модульную архитектуру
Вопросы для размышления
- Node.js использует один поток - как это влияет на горизонтальное масштабирование? Когда нужен cluster module vs PM2 vs несколько Docker-контейнеров?
- Express middleware chain - это цепочка ответственности (Chain of Responsibility паттерн). Какие другие паттерны GoF реализует Express middleware?
- Когда стоит мигрировать с Express на Fastify или Hono? Какие факторы влияют на это решение?