Запропонувати правкуПокращити цю статтюДопрацюйте відповідь до «Route параметри та query strings в Express?». Ваші зміни проходять модерацію перед публікацією.Потрібне підтвердженняКонтентЩо ви змінюєте🇺🇸EN🇺🇦UAПереглядЗаголовок (UA)Коротка відповідь (UA)**Route параметри та query strings** - два способи передати дані через URL в Express. Route параметри (`/users/:id`) потрапляють у `req.params`, query strings (`/users?page=2`) - у `req.query`. Обидва значення приходять як рядки, тому числа потрібно конвертувати через `parseInt()`. **Головне:** параметри для ідентифікації ресурсу, query strings для фільтрів і пагінації.Показується над повною відповіддю для швидкого нагадування.Відповідь (UA)Зображення**Route параметри та query strings** - два різних способи, якими Express читає вхідні дані з URL. Параметри живуть у самому шляху (`/users/:id`), query strings - після `?` (`/users?page=2`). Вони потрапляють у різні об'єкти і виконують різні завдання. ## Теорія ### TL;DR - Route параметри - частина структури шляху: `/users/:id` матчиться з `/users/123` і кладе `'123'` у `req.params.id` - Query strings йдуть після `?` і не впливають на маршрутизацію: `/users?page=2` все одно потрапить у хендлер `/users` - Правило вибору: параметр для "який ресурс", query string для "як його повернути" - Обидва значення приходять як рядки. `req.query.page` - це `'2'`, не `2` ### Швидкий приклад ```javascript const express = require('express'); const app = express(); // Route параметр: ідентифікує конкретного користувача app.get('/users/:id', (req, res) => { // GET /users/123 → req.params.id = '123' res.json({ userId: req.params.id }); }); // Query string: фільтрує або пагінує колекцію app.get('/users', (req, res) => { // GET /users?page=2&limit=10 const page = parseInt(req.query.page, 10) || 1; const limit = parseInt(req.query.limit, 10) || 20; res.json({ page, limit }); }); app.listen(3000); ``` Route параметри йдуть у `req.params`. Query значення йдуть у `req.query`. Це два різних об'єкти, вони ніколи не перетинаються. ### Головна різниця Express використовує бібліотеку `path-to-regexp` для компіляції рядків маршрутів у регулярні вирази під час реєстрації. Сегмент `:id` стає named capture group, і кожен запит перевіряється відповідно. Query strings до цього кроку взагалі не мають відношення. Вони йдуть після `?` в URL, і Node.js парсить їх окремо в `req.query`. Запит до `/users/123?sort=asc` матчиться з `/users/:id`, дає `req.params.id === '123'` і `req.query.sort === 'asc'` одночасно. ### Коли що використовувати - Конкретний ресурс за ID: `/users/:id`, `/posts/:postId` - Вкладені ресурси: `/api/users/:userId/orders/:orderId` - Фільтрація колекції: `/users?status=active&role=admin` - Пагінація і сортування: `/posts?page=2&sort=desc&limit=20` - Пошуковий запит: `/search?q=nodejs` Простий тест: якщо прибрати значення і URL перестає вказувати на щось конкретне, це route параметр. Якщо просто змінюється результат, то query string. ### Як це працює всередині При старті `path-to-regexp` (v6+ в Express 4.18+) компілює `/users/:id` у щось на кшталт `/users/([^/]+?)`. На кожен запит `router.match()` запускає цей regex і заповнює `req.params`. Query strings цю логіку оминають. Вбудований модуль Node.js `querystring` читає все після `?` в `req.url` і розбирає в звичайний об'єкт `req.query`. ### Типові помилки **Помилка: читати route параметр з `req.query`** ```javascript // Маршрут: app.get('/users/:id', ...) // GET /users/123 const id = req.query.id; // undefined, неправильний об'єкт const { id } = req.params; // '123', правильно ``` На code review це найчастіша помилка з роутингом у джунів. `req.params` і `req.query` не пов'язані між собою і ніколи не містять одні й ті самі дані. **Помилка: не конвертувати числа з query string** ```javascript // GET /users?page=2 const page = req.query.page; // '2', рядок const offset = (page - 1) * 10; // працює випадково через coercion // Правильно const page = parseInt(req.query.page, 10) || 1; // 2, число ``` Арифметика спрацює через неявну конверсію, але `page === 2` поверне `false`. Такі баги знаходити дуже важко. **Помилка: пошуковий запит у шляху** ```javascript // Незручно: пробіли і спецсимволи кодуються погано app.get('/search/:q', (req, res) => { ... }); // Краще: query strings обробляють кодування автоматично app.get('/search', (req, res) => { const q = req.query.q; // 'foo bar' вже декодовано Express-ом }); ``` **Помилка: ігнорувати масиви в query params** ```javascript // GET /posts?tag=js&tag=node // Express 4.18+: req.query.tag = ['js', 'node'] // Один ключ: req.query.tag = 'js' (рядок) // Безпечна нормалізація const tags = [].concat(req.query.tag || []); ``` Express повертає рядок коли ключ зустрічається один раз і масив коли кілька. Завжди нормалізуй, якщо маршрут приймає повторювані ключі. ### Де зустрічається - GitHub: `/repos/:owner/:repo` для ідентифікації, `/repos?sort=stars&type=public` для фільтрації - Stripe: `/v1/customers/:id` для конкретного клієнта, `?limit=10&starting_after=ch_123` для пагінації - Twitter: `/2/tweets/:id` для твіту, `?max_results=100&query=nodejs` для пошуку - Будь-який REST API дотримується тієї ж конвенції: параметри шляху для ідентифікації, query strings для опцій ### Питання з інтерв'ю **Q:** Яка різниця між `req.params`, `req.query` та `req.body`? **A:** `req.params` заповнюється зі сегментів шляху типу `:id`. `req.query` - з query string після `?`. `req.body` містить тіло запиту і доступний тільки після підключення `express.json()` або `express.urlencoded()` middleware. **Q:** Що станеться, якщо route параметр і query string мають однакове ім'я, наприклад `/users/:id?id=999`? **A:** Вони залишаються незалежними. `req.params.id` тримає значення з шляху, а `req.query.id` тримає `'999'`. Express їх не об'єднує. **Q:** Як Express декодує символи типу `/users/Joe%20Doe`? **A:** `path-to-regexp` декодує percent-encoded значення автоматично. `req.params.name` буде `'Joe Doe'`, а не `'Joe%20Doe'`. **Q:** Як зробити route параметр опціональним? **A:** `:id?` працює в Express 4.x, але два окремих маршрути (`/users/:id` і `/users`) зазвичай чистіше. Опціональні параметри легко створюють неочікувані перетини з іншими маршрутами. **Q:** Чому той самий ключ у query string іноді рядок, іноді масив? **A:** Express повертає рядок коли ключ зустрічається один раз і масив коли кілька. Щоб уникнути окремих перевірок: `[].concat(req.query.tag)` завжди дає масив. ## Приклади ### Базовий: пошук користувача за ID ```javascript // GET /api/users/42 app.get('/api/users/:id', (req, res) => { const userId = parseInt(req.params.id, 10); // Шукаємо користувача 42 в базі res.json({ userId }); }); ``` `req.params.id` - це завжди рядок. Конвертуй у число перед запитом до бази даних. ### Реальний сценарій: список репозиторіїв з пагінацією (GitHub-style) ```javascript // GET /api/users/octocat/repos?page=2&per_page=30&sort=updated app.get('/api/users/:username/repos', (req, res) => { const { username } = req.params; const page = parseInt(req.query.page, 10) || 1; const perPage = parseInt(req.query.per_page, 10) || 30; const sort = req.query.sort || 'updated'; // username - хто, page/perPage/sort - як res.json({ username, page, perPage, sort }); }); ``` `username` ідентифікує ресурс. `page`, `per_page` і `sort` контролюють як він повертається. Плутати їх місцями - помилка, яка ламає REST-конвенції і ускладнює роботу з API.Для рев’юераПримітка для модератора (необов’язково)Бачить лише модератор. Прискорює рев’ю.