Ідентифікація проти формування: вибір параметрів маршруту чи рядків запиту в Express
Дізнайтеся, коли значення слід використовувати як параметр маршруту Express, а коли — у рядку запиту, як читати req.params та req.query, а також як безпечно працювати з значеннями за замовчуванням та їх типами.
Візьмемо URL /users/42?sort=name&order=asc. Число 42 вказує на конкретного користувача; параметри sort=name&order=asc лише змінюють спосіб відображення результату. Плутання цих двох функцій, наприклад використання ID як фільтра чи фільтра як ID, є типовою причиною проблем у маршрутах Express. Прочитавши цей посібник, ви матимете простий тест для визначення того, до кого належить певне значення, а також зрозумієте, як Express обробляє кожен тип параметрів та яких несподіванок можна очікувати.
Параметри маршруту ідентифікують ресурс
Параметр маршруту — це іменована частина самої схеми шляху. Він повідомляє сервер про те, до якого саме ресурсу стосується запит.
/users/:id
Колонка позначає :id як місце для підстановки. Коли надходить запит на /users/42, Express знаходить відповідну схему та записує 42 як значення id. Та сама логіка застосовується до будь-якого ресурсу, який має ідентифікатор:
/users/42 → which user
/products/17 → which product
/orders/1042 → which order
Кожна з цих шляхів позначає саме одну річ. Без цього сегмента запитання „який саме?“ залишається без відповіді.
Рядки запиту формують відповідь
Рядок запиту — це все, що знаходиться після ?: список пар key=value, об’єднаних символом &. Він не вибирає ресурс, а натомість фільтрує, сортує, створює сторінку чи іншим чином змінює те, що повертається.
/users?sort=name&order=asc
Тут sort та order не змінюють сам ресурс (колекцію користувачів), а лише впливають на його відображення. Більш типові приклади:
/products?category=electronics&maxPrice=500
/search?q=laptop&page=2
Тест з одним запитанням для розрізнення їх
Запитайте, що станеться, якщо ви видалите значення:
- Якщо маршрут більше не має сенсу (ви не можете отримати „користувача“ без вказівки конкретного), значення є ідентифікатором та має знаходитися в шляху.
- Якщо маршрут все ще працює та просто повертає стандартний, невідфільтрований результат (усі користувачі у звичайному порядку), значення є модифікатором та має знаходитися у рядку запиту.
Цей тест також натякає на обробку помилок. Відсутній ресурс за параметром маршруту зазвичай повинен призводити до коду 404, тоді як фільтр, який нічого не знаходить, має повертати код 200 із порожнім списком. Щоб дізнатися більше про проектування URL, орієнтоване на ресурси, перегляньте REST API для початківців.
Читання параметрів маршруту за допомогою req.params
Параметри оголошуються з двокрапкою у шляху маршруту, а їхні значення з’являються у req.params під тими самими іменами:
app.get("/users/:id", (req, res) => {
const userId = req.params.id;
res.send(`Fetching user with ID: ${userId}`);
});
Запит на /users/42 встановлює req.params.id у значення рядка "42".
Кілька параметрів у одному шляху
Вкладені ресурси просто оголошують додаткові місця для значень:
app.get("/users/:userId/orders/:orderId", (req, res) => {
const { userId, orderId } = req.params;
res.send(`User ${userId}, Order ${orderId}`);
});
Для /users/42/orders/1042 деструктуризація дає userId, що дорівнює "42", та orderId, що дорівнює "1042". Імена параметрів мають бути унікальними в межах маршруту та описувати те, що вони ідентифікують; userId та orderId зрозуміліші, ніж два анонімних параметри id.
Читання рядків запиту за допомогою req.query
Значення запитів не потребують оголошення у маршруті. Express аналізує все, що йде після ?, та записує це у req.query:
app.get("/users", (req, res) => {
const { sort, order } = req.query;
res.send(`Sorting by ${sort}, order: ${order}`);
});
Для /users?sort=name&order=asc значення req.query.sort буде "name", а req.query.order — "asc". Сам маршрут залишається /users, тож один і той самий обробник обслуговує як звичайний запит, так і запит із сортуванням.
Встановлення значень за замовчуванням для необов’язкових параметрів
Оскільки клієнти часто не вказують значення запитів, обробники зазвичай використовують розумні значення за замовчуванням:
app.get("/products", (req, res) => {
const sort = req.query.sort || "default";
const page = req.query.page || 1;
res.send(`Sorting: ${sort}, Page: ${page}`);
});
Запит просто на /products все одно успішний завдяки значенням за замовчуванням. Зверніть увагу на одну тонкість: коли клієнт дійсно надсилає page=2, page є рядком "2", але коли його не вказано, це число 1. Такі змішані типи призводять до проблем пізніше (наприклад, до об’єднання рядків замість додавання). Обов’язково перетворюйте значення явно, наприклад, за допомогою Number(req.query.page) || 1, та перевіряйте результат перед використанням у запиті до бази даних.
Значення не завжди є простими рядками
Ключ, який повторюється у URL, наприклад ?tag=a&tag=b, надходить у вигляді масиву, а не рядка. Залежно від налаштувань парсера запиту, синтаксис дужок також може створювати вкладені об’єкти. Express 5 змінив стандартний парсер на простіший, ніж той, що використовував Express 4, тому перевірте документацію до вашої версії, якщо ви користуєтесь вкладеними об’єктами запитів. У будь-якому разі ніколи не припускайте тип значення запиту; розглядайте req.query як ненадійний вхідний даний.
Вибір того, що потрібно маршруту
Параметри шляху для конкретного ресурсу
app.get("/users/:id", ...) // one specific user
app.get("/products/:id", ...) // one specific product
app.get("/orders/:orderId", ...) // one specific order
Кожен з цих маршрутів посилається на один конкретний елемент. Якщо маршрут є беззмістовним без цього значення, включіть його до шляху.
Рядки запитів для фільтрації, сортування та сторінкування
app.get("/users", ...) // ?role=admin&status=active
app.get("/products", ...) // ?category=electronics&maxPrice=500&sort=price
app.get("/search", ...) // ?q=laptop&page=2
Кожен з цих варіантів має сенс без будь-якого запиту: „усі користувачі“, „усі продукти“ або порожня сторінка пошуку. Значення, які лише звужують або переупорядковують результати, є необов’язковими модифікаторами та мають стояти після ?.
Поєднання обох у одному маршруті
У реальних кінцевих точках часто використовуються обидва одночасно. Параметр вибирає власника, а запит звужує відповідні дані:
app.get("/users/:id/orders", (req, res) => {
const userId = req.params.id; // which user
const status = req.query.status; // optional filter: only their pending orders, for example
res.send(`Orders for user ${userId}, filtered by status: ${status || "all"}`);
});
Запит до /users/42/orders?status=pending звучить природно: 42 вказує, чиї замовлення, а status=pending — які саме з цих замовлень включити. Коли параметр status відсутній, обробник повідомляє „усі“, що відповідає ідеї про те, що відсутній модифікатор означає незфільтрований результат.
Поширені запитання
Чи може один маршрут використовувати обидва?
Так, і це дуже поширено. Наведений вище приклад є типовим шаблоном: ідентифікатор батьківського ресурсу разом із необов’язковими фільтрами для його дочірніх елементів.
Чи завжди необхідні параметри, а значення запиту — завжди необов’язкові?
Це сильна конвенція, а не жорстке правило. Express дійсно підтримує необов’язкові сегменти шляху, і ніщо не заважає API вимагати значення запиту. Проте практичний принцип залишається таким: обов’язкові ідентифікатори розміщуються у шляху, а необов’язкові модифікатори — у запиті з параметрами за замовчуванням.
Чи є req.params.id числом?
Ні. Усе, що отримується з URL, є рядком, навіть якщо воно виглядає числовим. Очікуйте його явно, наприклад за допомогою Number(req.params.id), та відхиляйте значення, які стають NaN, ще до звернення до бази даних.
Що робити, якщо бракує очікуваного значення запиту?
Ключ просто відсутній у req.query, тому його читання повертає undefined. Саме тому раніше показані стандартні значення є загальноприйнятою практикою.
Підсумок
Обидва типи значень знаходяться в одному URL, але виконують різні функції. Параметри маршруту, які читаються з req.params, визначають конкретну тему запиту. Стрінги запиту, які читаються з req.query, впливають на спосіб фільтрації, сортування чи сторінкування результатів та повинні мати значення за замовчуванням. Розглядайте їх як нетипові рядки ззовнішнього світу: перетворюйте та перевіряйте їх перед використанням, і ваша структура маршрутів залишатиметься передбачуваною у міру розширення API.
Пов’язана література
- Захист меж Express: один middleware Zod для обробки тіла запиту, параметрів та запитових рядків — Дізнайтеся, як перевіряти тіло запиту, параметри маршруту та запитові рядки в Express за допомогою одного багаторазово використовуваного middleware Zod, а також як він доповнює процес перевірки моделей у Sequelize.
- REST API для початківців: ресурси, методи, коди стану та відсутність стану — Посібник простою мовою про те, що таке REST API, п’ять принципів, які забезпечують його функціонування, де він використовується у реальних командах, та як створити та протестувати свій перший REST API.