Запропонувати правкуПокращити цю статтюДопрацюйте відповідь до «Які існують різні типи проміжного програмного забезпечення в Express.js?». Ваші зміни проходять модерацію перед публікацією.Потрібне підтвердженняКонтентЩо ви змінюєте🇺🇸EN🇺🇦UAПереглядЗаголовок (UA)Коротка відповідь (UA)**Middleware в Express.js** - функції, що виконуються між отриманням запиту і відправкою відповіді, з доступом до `req`, `res` і `next()`. ```js app.use((req, res, next) => { console.log(req.method); // виконується для кожного запиту next(); }); ``` Express має 5 типів: application-level (`app.use()`), router-level (`router.use()`), обробка помилок (4 параметри: `(err, req, res, next)`), вбудований (`express.json()`), сторонній (`helmet`, `cors`). **Головне правило:** обробники помилок монтуються останніми, в async middleware завжди викликай `next()`.Показується над повною відповіддю для швидкого нагадування.Відповідь (UA)Зображення**Middleware в Express.js** - функції, що виконуються між отриманням HTTP-запиту і відправкою відповіді, кожна з доступом до `req`, `res` і `next()`. ## Теорія ### TL;DR - Express має 5 типів middleware: application-level, router-level, обробка помилок, вбудований, сторонній - Уявляй це як конвеєр: кожна станція опрацьовує запит і передає його далі через `next()` - Application-level виконується глобально, router-level обмежений шляхом монтування - Обробник помилок використовує 4-параметровий підпис `(err, req, res, next)` і монтується останнім - Немає виклику `next()` у middleware - запит зависає назавжди ### Швидкий приклад ```js const express = require('express'); const app = express(); // Application-level: виконується для всіх запитів app.use((req, res, next) => { console.log(`${req.method} ${req.path}`); // GET /api/users next(); }); // Router-level: обмежений /api const router = express.Router(); router.use((req, res, next) => { console.log('Router hit'); // тільки для /api/* next(); }); app.use('/api', router); ``` Для `GET /api/users` спрацюють обидва логи по черзі. Для `GET /` тільки перший. Ця різниця і пояснює весь поділ між app і router. ### Типи middleware **1. Application-level (на рівні застосунку)** Прив'язується до об'єкта `app` через `app.use()` або `app.METHOD()`. Виконується глобально або для конкретного шляху. ```js app.use(express.json()); // для всіх маршрутів app.get('/users', (req, res, next) => { next(); }); // тільки GET /users ``` **2. Router-level (на рівні роутера)** Прив'язується до `express.Router()`. Поведінка така ж як у application-level, але обмежена шляхом монтування. Стандартний спосіб розбити великий API на модулі. ```js const userRouter = express.Router(); userRouter.use(authCheck); // виконується тільки для /api/users/* app.use('/api/users', userRouter); ``` **3. Error-handling (обробка помилок)** Express розпізнає цей тип за 4-параметровим підписом `(err, req, res, next)`. Переходить до нього, коли будь-який інший middleware викликає `next(err)`. Завжди монтується останнім. ```js app.use((err, req, res, next) => { res.status(err.status || 500).json({ error: err.message }); }); ``` **4. Built-in (вбудований)** Входить до складу Express починаючи з версії 4.16. Три функції закривають найпоширеніші потреби: ```js app.use(express.json()); // парсить JSON-тіла app.use(express.urlencoded({ extended: true })); // парсить форми app.use(express.static('public')); // роздає статичні файли ``` **5. Third-party (сторонній)** npm-пакети, що підключаються до тієї ж системи middleware: ```js app.use(require('helmet')()); // заголовки безпеки app.use(require('cors')()); // CORS-заголовки app.use(require('morgan')('dev')); // логування запитів ``` ### Порядок виконання Express тримає стек функцій middleware для кожного застосунку або роутера, виконуючи їх у порядку монтування. При отриманні запиту Express проходить стек зверху вниз. Кожна функція або викликає `next()` щоб продовжити, або відправляє відповідь щоб зупинити ланцюжок, або викликає `next(err)` щоб перейти до обробника помилок. ```js app.use(cors()); // 1 app.use(helmet()); // 2 app.use(express.json()); // 3 app.use('/api', routes); // 4 - обробники маршрутів app.use(notFound); // 5 - 404 app.use(errorHandler); // 6 - завжди останнє ``` Стек роутера це підстек застосунку, підключений до конкретного шляху. Тому `userRouter.use(authCheck)` разом з `app.use('/api', userRouter)` означає, що `authCheck` запускається тільки коли шлях починається з `/api`. ### Типові помилки **Забутий `next()` в async middleware** ```js // Неправильно - запит зависає назавжди app.use(async (req, res, next) => { await db.connect(); // next() не викликається }); // Правильно app.use(async (req, res, next) => { try { await db.connect(); next(); } catch (err) { next(err); } }); ``` **Обробник помилок змонтований занадто рано** ```js // Неправильно - обробник помилок до маршрутів app.use((err, req, res, next) => { res.status(500).send('Error'); }); app.get('/', (req, res) => res.send('Hi')); // Правильно app.get('/', (req, res) => res.send('Hi')); app.use((err, req, res, next) => { res.status(500).send('Error'); }); ``` **`express.json()` підключений після роутера** Проблема з порядком підключення парсера тіла трапляється з більшістю розробників у першому реальному Express-проекті. Це перше що треба перевіряти коли `req.body` показує `undefined`. ```js // Неправильно - req.body буде undefined в userRouter app.use('/api', userRouter); app.use(express.json()); // запізно // Правильно app.use(express.json()); // спочатку парсер app.use('/api', userRouter); ``` **Необроблені async-помилки в Express 4** Async-функція яка кидає виключення не викликає `next(err)` автоматично в Express 4. Помилка повністю обходить обробник. ```js // Неправильно в Express 4 - помилка не потрапить до обробника app.get('/data', async (req, res, next) => { throw new Error('Boom'); }); // Правильно - явний try/catch app.get('/data', async (req, res, next) => { try { const data = await fetchData(); res.json(data); } catch (err) { next(err); } }); ``` Express 5 (наразі в beta) обробляє async-виключення автоматично. **Конфліктні шляхи роутерів перекривають один одного** ```js app.use('/users', userRouter); // спрацьовує app.use('/users/:id', idRouter); // ніколи не спрацює, перекритий ``` Виправлення: реєструй роутери від специфічних до загальних, або об'єднай логіку в один роутер. ### Де зустрічається в реальних проектах - `morgan` для логування майже в кожному Express-застосунку: `app.use(morgan('combined'))` - `helmet` як базовий мінімум безпеки: `app.use(helmet())` - Router-level авторизація для групи маршрутів `/api/v1` в REST API - NestJS використовує application-level middleware для guards та interceptors під капотом - NextAuth.js обгортає свою групу маршрутів `/api/auth` в `express.Router()` ### Питання на співбесіді **Q:** Який порядок виконання для вкладених роутерів? **A:** Спочатку application-level middleware, потім middleware зовнішнього роутера, потім внутрішнього, потім обробник маршруту. `next()` з'єднує їх лінійно зверху вниз. **Q:** Яка різниця між `app.METHOD()` і `app.use()`? **A:** `app.get()` відповідає лише GET-запитам і точному шляху. `app.use()` відповідає всім HTTP-методам і будь-якому шляху з відповідним префіксом. Тому глобальний middleware завжди використовує `app.use()`. **Q:** Що буде якщо middleware викличе `next()` двічі? **A:** Другий виклик ігнорується. Помилки немає, але поведінка непередбачувана якщо відповідь вже відправлена. **Q:** Яка різниця між `express.json()` і `body-parser`? **A:** На практиці різниці немає. `body-parser` перенесли до ядра Express у версії 4.16. `express.json()` викликає той самий код. **Q (senior):** В мікросервісі з 50+ функцій middleware як уникнути проблем з продуктивністю без втрати порядку виконання? **A:** Групуй пов'язані функції в масиви: `const apiPipeline = [cors(), helmet(), express.json()]`, потім `app.use('/api', apiPipeline, routes)`. Профілюй за допомогою `clinic.js` або `0x`. Уникай синхронних блокуючих операцій в middleware. ## Приклади ### Базовий: глобальне логування і парсинг JSON ```js const express = require('express'); const app = express(); // Вбудований: парсить JSON-тіла до того як маршрути читають req.body app.use(express.json()); // Application-level: логування кожного запиту app.use((req, res, next) => { console.log(`${req.method} ${req.path}`); // GET /users next(); }); app.get('/users', (req, res) => { res.json([{ id: 1, name: 'Alice' }]); }); app.listen(3000); ``` Кожен запит спочатку потрапляє до логера, потім до обробника маршруту. Якщо поміняти ці два `app.use()` місцями, логер все одно працює, але POST-маршрут що читає `req.body` отримає `undefined`. ### Середній: авторизація на рівні роутера ```js const express = require('express'); const app = express(); const userRouter = express.Router(); app.use(express.json()); // Router-level: перевірка токена тільки для /api/users userRouter.use((req, res, next) => { if (req.headers.authorization !== 'Bearer secret') { return res.status(401).json({ error: 'Unauthorized' }); } next(); }); userRouter.post('/', (req, res) => { res.status(201).json({ id: 1, name: req.body.name }); // { id: 1, name: 'Alice' } }); app.use('/api/users', userRouter); app.listen(3000); // POST /api/users без токена -> 401 // POST /api/users з Bearer secret -> 201 ``` Перевірка авторизації виконується тільки для `/api/users/*`. Публічний маршрут `GET /health` її не торкається. Саме в цьому практична перевага router-level middleware над глобальним. ### Для досвідчених: async-обробка помилок через весь ланцюжок ```js const express = require('express'); const app = express(); app.use(express.json()); const findUser = (id) => { if (id !== '1') { const err = new Error('User not found'); err.status = 404; throw err; } return { id: 1, name: 'Alice' }; }; app.get('/users/:id', async (req, res, next) => { try { const user = findUser(req.params.id); res.json(user); } catch (err) { next(err); // переходить до обробника помилок нижче } }); // 4 параметри, монтується останнім app.use((err, req, res, next) => { console.error(err.stack); // Error: User not found\n at ... res.status(err.status || 500).json({ error: err.message }); }); app.listen(3000); // GET /users/1 -> { id: 1, name: 'Alice' } // GET /users/99 -> 404 { error: 'User not found' } ``` Якщо прибрати `try/catch` і кидати виключення напряму з async-обробника в Express 4, помилка обходить `next(err)` повністю. Явне загортання async-коду обов'язкове поки Express 5 не стане стабільним.Для рев’юераПримітка для модератора (необов’язково)Бачить лише модератор. Прискорює рев’ю.