Skip to main content

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

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

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.

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

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

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

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