Запропонувати правкуПокращити цю статтюДопрацюйте відповідь до «Що таке проміжне програмне забезпечення в Express.js і як воно працює?». Ваші зміни проходять модерацію перед публікацією.Потрібне підтвердженняКонтентЩо ви змінюєте🇺🇸EN🇺🇦UAПереглядЗаголовок (UA)Коротка відповідь (UA)**Middleware** в Express.js - це функція з доступом до `req`, `res` і `next()`, яка виконується між вхідним HTTP-запитом і обробником маршруту. ```js app.use((req, res, next) => { console.log(`${req.method} ${req.url}`); next(); // передати до наступного middleware або маршруту }); ``` **Ключове:** middleware виконується в порядку реєстрації. Без виклику `next()` ланцюг зупиняється.Показується над повною відповіддю для швидкого нагадування.Відповідь (UA)Зображення**Middleware** в Express.js - це функція, яка стоїть між вхідним HTTP-запитом і обробником маршруту, має доступ до `req`, `res` і функції `next()` для передачі управління далі. ## Теорія ### TL;DR - Черга на паспортний контроль: кожен пункт перевірки (middleware) перевіряє документи (`req`), ставить штамп (`res`), пропускає далі (`next()`). Будь-який пункт може зупинити. - Middleware виконується в тому порядку, в якому ти його реєструєш. Порядок важливий. - Не викликав `next()` - запит зависає. Або відправ відповідь, або передай управління. - Error middleware приймає 4 аргументи: `(err, req, res, next)`. Реєструй його останнім. - Shared логіка для багатьох маршрутів (логування, auth) - у middleware. Бізнес-логіка - в обробнику маршруту. ### Швидкий приклад ```js const express = require('express'); const app = express(); // Логування - виконується на кожному запиті app.use((req, res, next) => { console.log(`${req.method} ${req.url}`); // GET /users next(); // передаємо управління далі }); // Auth - тільки для /users app.use('/users', (req, res, next) => { if (req.headers.authorization) { next(); // токен є, продовжуємо } else { res.status(401).send('Unauthorized'); // зупиняємось тут } }); // Обробник маршруту - досягається тільки якщо auth пройшов app.get('/users', (req, res) => { res.json({ users: ['Alice', 'Bob'] }); }); app.listen(3000); ``` `GET /users` з токеном: запит логується, auth проходить, повертаємо користувачів. Без токена: 401 і все. ### Як це працює всередині Express будує стек middleware-функцій під час налаштування додатку. Їх зберігають як шари в `app._router.stack`. Коли приходить запит, Node викликає `app.handle(req, res)`, який проходить по стеку один шар за одним. Кожен шар виконує свою функцію і передає `next` як диспетчер для переходу до наступного відповідного шару або маршруту. Async middleware не викликає `next()` автоматично. Це треба робити вручну, інакше запит зависає. Більшість розробників стикаються з цим при першому знайомстві з async middleware в Express. ### Типи middleware **Application-level** middleware реєструється через `app.use()` і виконується на кожному запиті (або кожному запиті з певним префіксом шляху). **Route-level** middleware прив'язується до конкретного маршруту: `app.get('/path', middleware, handler)`. Можна вказати кілька функцій перед кінцевим обробником. **Error-handling** middleware має рівно 4 параметри: `(err, req, res, next)`. Express розпізнає цей підпис і автоматично направляє туди помилки при виклику `next(err)`. **Вбудоване**: `express.json()`, `express.urlencoded()`, `express.static()` закривають більшість потреб без додаткових пакетів. **Стороннє**: `morgan` для логування, `helmet` для заголовків безпеки, `cors` для cross-origin запитів, `express-rate-limit` для обмеження кількості запитів. ### Коли використовувати - Спільна логіка для всіх маршрутів (логування, CORS, заголовки безпеки): `app.use()` на початку файлу. - Перевірки для конкретного маршруту (auth, валідація): передай middleware безпосередньо у визначення маршруту. - Обробка помилок: `app.use((err, req, res, next) => {...})` після всіх маршрутів. - Статичні файли: `express.static('public')`. - Бізнес-логіка належить обробнику маршруту, не middleware. ### next(), next('route') і next(err) ```js next() // перейти до наступного middleware або обробника маршруту next('route') // пропустити решту middleware в стеку, перейти до наступного маршруту next(err) // перейти прямо до обробника помилок ``` `next('route')` - деталь рівня senior. Дозволяє писати fallback маршрути: виконав middleware, вирішив що цей маршрут не підходить, і передав запит до наступного відповідного визначення маршруту. ### Типові помилки **Забутий next() в async middleware** ```js // Неправильно - запит зависає назавжди app.use(async (req, res, next) => { req.data = await db.query('SELECT * FROM users'); // next() не викликається }); // Правильно app.use(async (req, res, next) => { try { req.data = await db.query('SELECT * FROM users'); next(); } catch (err) { next(err); } }); ``` Express виконує middleware синхронно. Async функція одразу повертає Promise, але Express не чекає на нього. `await` завершується, а `next()` так і не викликається. **Глобальне middleware зареєстроване після маршрутів** ```js // Неправильно - logger ніколи не спрацює для /users app.get('/users', handler); app.use(logger); // Правильно app.use(logger); app.get('/users', handler); ``` Маршрути матчаться першими. `app.use()`, зареєстрований після маршруту, не виконується для цього маршруту. **Подвійний виклик next()** ```js // Неправильно - запускає наступне middleware двічі app.use((req, res, next) => { next(); next(); // другий виклик ламає стек }); ``` Дублює логування, обробку і часто дає помилку "headers already sent". Виклик `next()` - рівно один раз на middleware. **Error handler перед маршрутами** ```js // Неправильно app.use(errorHandler); app.get('/users', handler); // Правильно - error handler завжди останній app.get('/users', handler); app.use(errorHandler); ``` **Неправильний порядок залежностей у req** Якщо твоє middleware читає `req.body`, переконайся що `express.json()` зареєстрований раніше. Інакше `req.body` буде `undefined` і middleware мовчки не спрацює. ### Де зустрічається в реальних проектах - `morgan`: логування HTTP-запитів у production API - `helmet`: заголовки безпеки (більше 1 мільйона npm-проектів) - `passport.js`: автентифікація через `app.use(passport.initialize())` - `cors`: cross-origin запити для зв'язки frontend і backend - `express-rate-limit`: захист `/api` маршрутів від зловживань - `express.json()`: замінив старий пакет `body-parser` (вбудовано починаючи з Express 4.16) ### Питання на інтерв'ю **Q:** Яка різниця між `app.use()` і `router.use()`? **A:** `app.use()` реєструє middleware глобально на рівні додатку. `router.use()` обмежує його до sub-router, тобто middleware виконується тільки для маршрутів цього роутера. Зручно при розбивці великого API на окремі модулі. **Q:** Чи можна використовувати async/await у middleware? Які підводні камені? **A:** Так. Але `next()` треба викликати вручну після завершення async-операції, а весь код обгорнути в `try/catch` для виклику `next(err)` при помилці. Необроблені відхилення (unhandled rejections) крашають Node-процес у версіях 15+ і мовчки дропають запит у старіших. **Q:** Як працює `next('route')`? **A:** Пропускає всі middleware, що залишились у поточному стеку, і переходить до наступного відповідного маршруту. Зручно для умовної маршрутизації: перевірив прапорець і передав запит на fallback обробник. **Q:** Що станеться якщо викликати `res.send()` і потім `next()`? **A:** Відповідь вже надіслано. `next()` просуне стек далі, але будь-яке middleware що спробує записати в `res` отримає помилку "headers already sent". Додай `return` перед `res.send()`, щоб уникнути цього. **Q:** Як впливає великий стек middleware на продуктивність? **A:** Мінімально. Синхронний прохід по стеку займає приблизно 1 мікросекунду на шар. Справжнє вузьке місце - async I/O, не глибина стека. Для вимірювання використовуй `clinic.js`. ## Приклади ### Базовий: логування запитів і auth-перевірка ```js const express = require('express'); const app = express(); app.use(express.json()); // Логер - фіксує метод, URL і позначає час старту app.use((req, res, next) => { req.startTime = Date.now(); console.log(`--> ${req.method} ${req.url}`); next(); }); // Auth для захищених маршрутів function requireAuth(req, res, next) { const token = req.headers.authorization?.split(' ')[1]; if (!token) return res.status(401).json({ error: 'Token required' }); req.user = { id: 42 }; // в реальному коді: верифікація JWT next(); } app.get('/public', (req, res) => { res.json({ message: 'Відкрито для всіх' }); }); app.get('/private', requireAuth, (req, res) => { res.json({ message: 'Hello', userId: req.user.id }); }); app.listen(3000); ``` Логер виконується для обох маршрутів. `requireAuth` - тільки для `/private`. Якщо токен відсутній, обробник маршруту не виконується. ### Середній: async middleware з правильною обробкою помилок ```js const fakeDB = { findUser: async (token) => { if (token === 'valid') return { id: 1, name: 'Alice', admin: true }; throw new Error('User not found'); } }; app.get('/profile', // Крок 1: завантажуємо користувача з DB по токену async (req, res, next) => { try { req.user = await fakeDB.findUser(req.query.token); next(); } catch (err) { next(err); // передаємо до error handler, не res.send() } }, // Крок 2: перевіряємо права адміна (req, res, next) => { if (!req.user.admin) { return next(new Error('Access denied')); } next(); }, // Крок 3: відповідаємо (req, res) => { res.json(req.user); } ); // Центральний обробник помилок - останній, 4 аргументи app.use((err, req, res, next) => { console.error(err.message); res.status(500).json({ error: err.message }); }); ``` Без `next(err)` в async middleware - необроблене відхилення Promise крашає процес (Node 15+) або мовчки дропає запит у старіших версіях. `try/catch` з `next(err)` - правильний патерн. ### Senior: rate limiter з контекстом запиту ```js const express = require('express'); const app = express(); // Додаємо час старту та ID користувача до кожного запиту app.use((req, res, next) => { req.startTime = Date.now(); req.userId = req.headers['x-user-id'] || 'anonymous'; next(); }); // Простий in-memory rate limiter для /api маршрутів const requestCounts = {}; app.use('/api', (req, res, next) => { const key = req.userId; requestCounts[key] = (requestCounts[key] || 0) + 1; if (requestCounts[key] > 100) { return res.status(429).json({ error: 'Rate limit exceeded' }); } next(); }); app.get('/api/users/:id', (req, res) => { const elapsed = Date.now() - req.startTime; console.log(`${req.userId} запросив користувача ${req.params.id} за ${elapsed}ms`); res.json({ id: req.params.id, name: 'Jane' }); }); app.listen(3000); ``` Перше middleware збагачує `req` контекстом, який вільно читається далі по стеку. Поширений патерн у production API: одне middleware заповнює `req.user`, `req.requestId`, `req.startTime`, а решта стека використовує ці значення без повторних запитів до бази.Для рев’юераПримітка для модератора (необов’язково)Бачить лише модератор. Прискорює рев’ю.