Идентификация против формы: выбор параметров маршрута или строк запроса в Express
Узнайте, когда значение следует использовать в параметре маршрута Express, а когда — в строке запроса, как читать req.params и req.query, а также как безопасно обрабатывать значения по умолчанию и типы данных.
Возьмем URL /users/42?sort=name&order=asc. Число 42 указывает на конкретного пользователя; параметры sort=name&order=asc лишь влияют на формат отображения ответа. Смешивание этих двух функций, например использование ID в качестве фильтра или наоборот, является распространенной причиной возникновения сложных маршрутов в Express. Прочитав этот гид, вы сможете просто проверить, к какой категории относится тот или иной параметр, а также понять, как Express обрабатывает каждый тип параметров и какие неожиданности могут возникнуть.
Параметры маршрута идентифицируют ресурс
Параметр маршрута — это именованный элемент, входящий в саму схему пути. Он указывает серверу, о каком именно ресурсе идет речь в запросе.
/users/:id
Двоеточие обозначает :id как место замены. Когда поступает запрос на /users/42, Express сопоставляет этот шаблон и сохраняет значение 42 в качестве значения id. Та же логика применяется ко всем ресурсам, имеющим идентификатор:
/users/42 → which user
/products/17 → which product
/orders/1042 → which order
Каждый из этих путей обозначает ровно один объект. Без этого сегмента вопрос «какой именно?» остается без ответа.
Строки запросов формируют ответ
Строка запроса — это всё, что находится после знака ?: список пар ключ=значение, соединённых символом &. Она не выбирает ресурс, а лишь фильтрует, сортирует, формирует страницирование или иным образом изменяет содержимое ответа.
/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: один мидлвэрт Zod для обработки тела запроса, параметров и строкы запроса — Узнайте, как использовать один повторно применимый мидлвэрт Zod для валидации тела запросов Express, параметров маршрутов и строк запроса, а также как он дополняет процесс валидации моделей Sequelize.
- REST API для начинающих: ресурсы, методы, коды состояния и отсутствие состояния — Понятное руководство о том, что такое REST API, пять принципов, обеспечивающих его работу, где он используется в реальных командах, и как создать и протестировать свой первый REST API.