Параметры пути и query-строка

Параметры пути (/users/:id) и query-строка (?sort=name) делают маршруты гибкими.
«Один маршрут /users/:id обслуживает всех пользователей сразу — id подставляется на лету.»

Реальные API не пишут отдельный маршрут под каждого пользователя. Вместо этого используют параметры пути — динамические части url. А чтобы передавать опции вроде сортировки или фильтра, применяют query-строку. В этом уроке разберём оба механизма и научимся безопасно их читать.

Параметры пути

Двоеточие в пути объявляет параметр. Его значение Express кладёт в req.params:

// GET /users/42  ->  req.params.id === '42'
app.get('/users/:id', (req, res) => {
  const id = req.params.id;
  res.json({ id, name: 'Пользователь ' + id });
});

// можно несколько параметров
// GET /posts/10/comments/3
app.get('/posts/:postId/comments/:commentId', (req, res) => {
  res.json(req.params);
  // { postId: '10', commentId: '3' }
});

Важно: параметры всегда строки. Если ждёшь число, преобразуй явно через Number(id) и проверь результат.

Query-строка

Всё, что идёт после ?, Express парсит в объект req.query:

// GET /users?sort=name&page=2
app.get('/users', (req, res) => {
  const { sort, page } = req.query;
  res.json({ sort, page });
  // { sort: 'name', page: '2' }
});

Парсим query сами

Чтобы понять, что Express делает за кулисами, разберём query-строку вручную в браузере. Алгоритм простой: разбить по &, затем каждую пару по =:

function parseQuery(qs) {
  const result = {};
  qs.split('&').forEach(pair => {
    const [key, value] = pair.split('=');
    result[decodeURIComponent(key)] = decodeURIComponent(value || '');
  });
  return result;
}

const q = parseQuery('sort=name&page=2&q=%D0%90%D0%BD%D1%8F');
console.log(q.sort);  // name
console.log(q.page);  // 2
console.log(q.q);     // Аня (раскодировано из %..)

Express использует более умный парсер (с поддержкой массивов и вложенности), но идея ровно эта.

Как работает под капотом

При матчинге маршрута Express превращает :id в группу регулярного выражения. Когда url совпадает, захваченные группы попадают в req.params по именам. Query-строка парсится отдельно из части url после ? и кладётся в req.query. Оба объекта содержат только строки — об этом легко забыть.

/users/42?sort=name
   |        |
   |        +-> req.query  = { sort: 'name' }
   +----------> req.params = { id: '42' }

Частые ошибки

  • Сравнивать строку с числом. req.params.id === 42 всегда false: слева строка. Преобразуй явно.
  • Доверять query вслепую. Клиент может прислать что угодно. Значения нужно валидировать перед использованием в логике и тем более в запросах к БД.
  • Путать params и query. Params — часть пути и обычно обязательны; query — опции после ? и обычно необязательны.

Best practices

  • Преобразуй и проверяй числовые параметры: const id = Number(req.params.id); if (!Number.isInteger(id)) return res.status(400)...
  • Используй query для сортировки, пагинации и фильтров, а не для идентификаторов ресурса.
  • Задавай разумные значения по умолчанию для query (например, page=1).

Итоги

Параметры пути и query-строка делают один маршрут универсальным. Помни: и то, и другое приходит строками и требует валидации. Дальше разберём, как читать тело запроса и заголовки — без этого не принять данные формы или JSON от клиента.

Опциональные части и звёздочка

Пути в Express умеют больше, чем простой :id. Можно делать сегменты необязательными, ловить остаток пути и матчить по шаблону. В Express 5 движок путей обновили на новую версию библиотеки разбора, которая безопаснее к атакам через специально подобранные строки (ReDoS) и чуть строже к синтаксису, чем в четвёртой версии. Практический вывод: не увлекайся сложными регулярными путями. Если логика выбора маршрута становится хитрой, это сигнал вынести разбор в обычный код обработчика, где его легче читать, тестировать и защищать. Простые понятные пути почти всегда лучше виртуозных регулярок.

Проверьте себя
1. Какого типа значения в req.params?
AЧисла
BСтроки
CОбъекты
DЗависит от пути
2. Для чего обычно используют query-строку?
AДля идентификатора ресурса
BДля сортировки, пагинации и фильтров
CДля передачи пароля
DДля выбора HTTP-метода