Route параметри та query strings в Express?
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
Швидкий приклад
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
// Маршрут: 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
// 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. Такі баги знаходити дуже важко.
Помилка: пошуковий запит у шляху
// Незручно: пробіли і спецсимволи кодуються погано
app.get('/search/:q', (req, res) => { ... });
// Краще: query strings обробляють кодування автоматично
app.get('/search', (req, res) => {
const q = req.query.q; // 'foo bar' вже декодовано Express-ом
});Помилка: ігнорувати масиви в query params
// 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
// 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)
// 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.
Коротка відповідь
Для співбесідиКоротка відповідь допоможе вам впевнено відповідати на цю тему під час співбесіди.