Skip to main content

Що таке Express.js і чому його використовують?

Express.js - це мінімальний веб-фреймворк для Node.js, який додає маршрутизацію, middleware та HTTP-утиліти поверх вбудованого модуля http.

Теорія

TL;DR

  • Express для Node.js, як підготовча станція для кухаря: Node дає тобі піч і сировину, Express додає ножі, дошки і рецепти
  • Головна різниця: 15+ рядків ручного парсингу запитів у Node.js стають 3 декларативними рядками у Express
  • Express автоматично обробляє URL-маршрутизацію, парсинг JSON і заголовки відповідей
  • Правило вибору: бери Express для будь-якого Node.js веб-додатку, якщо не потрібні нуль залежностей або 10k+ req/s (тоді Fastify)

Швидкий приклад

js
// Чистий Node.js: ручна маршрутизація, ручні заголовки, все вручну const http = require('http'); const server = http.createServer((req, res) => { if (req.url === '/users' && req.method === 'GET') { res.writeHead(200, { 'Content-Type': 'application/json' }); res.end(JSON.stringify([{ id: 1, name: 'Alice' }])); } else { res.writeHead(404); res.end('Not Found'); } }); server.listen(3000); // Express: той самий результат, 3 рядки const express = require('express'); const app = express(); app.get('/users', (req, res) => res.json([{ id: 1, name: 'Alice' }])); app.listen(3000);

Обидва дають однакову відповідь. Express прибирає весь рядковий пошук по req.url і ручне виставлення заголовків.

Що Express насправді робить

Модуль http у Node.js дає тобі сирий об'єкт запиту: рядок URL, рядок методу і тіло як буфер. Все потрібно парсити самостійно. Express обгортає цей об'єкт роутером (radix-дерево для швидкого пошуку шляхів), підключає парсери через middleware і дає тобі res.json(), res.status() та інші хелпери. Ти пишеш обробники маршрутів замість ланцюжків if/else.

Стек middleware (проміжних функцій) - це центральна ідея. Кожен виклик app.use() додає функцію в чергу. Коли приходить запит, Express запускає кожну функцію по порядку, передаючи через них req і res. Якщо middleware викликає next(), запускається наступна функція. Якщо викликає res.send(), ланцюжок там і зупиняється.

Коли використовувати Express

  • REST API або веб-додаток: Express - вибір за замовчуванням
  • Прототипування: найшвидший шлях від ідеї до запущеного сервера
  • Full-stack додаток з авторизацією і базою даних: Express плюс стек middleware (cors, helmet, body-parser)
  • Serverless або edge-функції з багатьма маршрутами: Express підходить; менше 10 маршрутів, hono.js буде легшим
  • Мікросервіс з кастомним протоколом: пропускай Express, використовуй http напряму
  • 10k+ запитів на секунду в продакшені: Fastify приблизно вдвічі швидший; Express впевнено тягне до ~5k req/s

Альтернативи на вибір

ФреймворкСтильНайкраще підходить
ExpressМінімальний, гнучкийREST API, full-stack додатки
FastifyШвидкий, на основі схемНавантажені API
KoaAsync-first, менше ядроНові проекти, кастомний middleware
NestJSСтруктурований, TypeScriptКорпоративні додатки
hono.jsКомпактний, для edgeServerless, Cloudflare Workers

Koa варто знати для співбесіди. Він використовує async/await нативно там, де Express використовує callbacks, і його ядро значно менше. Для нових проектів Koa - цілком валідний вибір. Коли потрібна велика екосистема плагінів, Express виграє.

Типові помилки

Відсутній express.json() перед маршрутами. Це ламає приблизно 40% перших Express-додатків. Без middleware req.body залишається undefined, і будь-який доступ до JSON падає з помилкою.

js
// Неправильно: req.body тут undefined app.post('/user', (req, res) => res.send(req.body.name)); // TypeError // Правильно: парсити JSON глобально перед будь-яким маршрутом app.use(express.json()); app.post('/user', (req, res) => res.send(req.body.name)); // "Alice"

Неправильний порядок middleware. Middleware запускається зверху вниз. Якщо зареєструвати маршрут до express.json(), той маршрут отримає непарсинговане тіло.

js
// Неправильно: маршрут бачить req.body як undefined app.post('/api/user', (req, res) => res.send(req.body.name)); app.use(express.json()); // запізно // Правильно app.use(express.json()); // обов'язково першим app.post('/api/user', (req, res) => res.send(req.body.name)); // працює

Подвійний виклик res.send(). Node кидає "Cannot set headers after they are sent". Виправляється раннім return.

js
// Неправильно: два send в одному обробнику if (err) res.status(500).send('Error'); res.json({ ok: true }); // краш // Правильно if (err) return res.status(500).send('Error'); res.json({ ok: true });

Відсутній next() у middleware. Запит зависає. Клієнт не отримує відповідь, в продакшен-моніторах спрацьовують таймаути.

js
// Неправильно: запит зависає назавжди app.use((req, res, next) => { console.log(req.method); // забули next() }); // Правильно app.use((req, res, next) => { console.log(req.method); next(); });

Де зустрічається в реальних проектах

  • Strapi: headless CMS з 1M+ завантаженнями, побудований на Express для REST-ендпоінтів і адмін-панелі
  • Ghost: блогова платформа, Express обробляє динамічні маршрути і теми
  • PayloadCMS: Express плюс TypeScript для e-commerce бекендів, 10k+ розробників
  • Next.js API routes: внутрішньо використовує Express-подібний роутер для обробників /pages/api
  • Більшість внутрішніх Node.js інструментів стартують з Express, а до Fastify переходять лише тоді, коли throughput стає реальною проблемою

Я помічав, що команди використовують Express роками без досягнення його performance-межі, бо справжнє вузьке місце - це запит до бази даних. Якщо запит займає 50ms, overhead Express у 0.1ms просто не має значення.

Питання на співбесіді

Q: Що таке middleware у Express і як це працює?
A: Middleware - це функція з сигнатурою (req, res, next). Express викликає їх по порядку для кожного запиту. Використовується для парсингу тіла, перевірки авторизації, логування або додавання даних до req. Якщо middleware не викликає next(), ланцюжок зупиняється і клієнт не отримує відповідь.

Q: Як Express обробляє async-помилки?
A: Автоматично - ніяк. Якщо в async-обробнику виникає виняток, Express його не зловить. Потрібно або загортати кожен обробник у try/catch і викликати next(err), або використовувати пакет express-async-errors, який патчить роутер для перехоплення відхилень Promise.

Q: В чому різниця між app.get() і router.get()?
A: app - це глобальний екземпляр Express. router - це міні-Express-додаток, який можна монтувати на будь-який шлях. Використовуй router для групування маршрутів за префіксом: app.use('/api', router) монтує всі маршрути роутера під /api. Це тримає великі кодові бази модульними без одного гігантського файлу.

Q: Як маршрутизація Express масштабується до сотень маршрутів?
A: Express використовує radix-дерево (layered path trie) для пошуку маршрутів. Пошук займає O(k), де k - довжина шляху, а не O(n) маршрутів. Тому 1000 маршрутів не уповільнять пошук порівняно з 10.

Q: Express проти Koa - коли обрати Koa?
A: Koa використовує async/await нативно і має менше ядро без вбудованого middleware. Гарний вибір для нових проектів, де потрібен повний контроль над стеком. Express має більшу екосистему плагінів і більше прикладів у спільноті. Команди на наявних кодових базах зазвичай залишаються з Express, бо вартість переходу не виправдана.

Приклади

Базовий REST API з парсингом JSON

js
const express = require('express'); const app = express(); app.use(express.json()); // парсинг JSON у тілі запиту app.get('/users', (req, res) => { res.json([{ id: 1, name: 'Alice' }]); }); app.post('/users', (req, res) => { const { name } = req.body; // працює завдяки express.json() res.status(201).json({ id: 2, name }); }); app.listen(3000, () => console.log('Запущено на порту 3000'));

app.use(express.json()) виконується перед кожним маршрутом. POST до /users з JSON-тілом автоматично отримує заповнений req.body. Без цього рядка req.body залишається undefined і деструктуризація падає.

Обробник webhook з правильним порядком middleware

Ось як виглядає перевірка Stripe webhook у реальному Express-додатку:

js
const express = require('express'); const app = express(); // Сирий буфер потрібен для перевірки підпису - обов'язково до express.json() app.use('/webhook/stripe', express.raw({ type: 'application/json' })); app.use(express.json()); // JSON для всього іншого app.post('/webhook/stripe', (req, res) => { const sig = req.headers['stripe-signature']; if (!verifySignature(sig, req.body)) { return res.status(400).send('Invalid signature'); } console.log('Event:', req.body.type); // 'payment_intent.succeeded' res.json({ received: true }); }); app.listen(3000);

Маршрут /webhook/stripe потребує сирого буфера для перевірки підпису. Все інше отримує парсингований JSON. Порядок middleware контролює, який парсер спрацює першим, тому специфічний маршрут реєструється до глобального. Це реальний патерн з PayloadCMS та подібних проектів.

Коротка відповідь

Для співбесіди
Premium

Коротка відповідь допоможе вам впевнено відповідати на цю тему під час співбесіди.

Дочитали статтю?