Express / 37 минут

Query, пагинация и фильтры

Проверяйте query-параметры как недоверенные данные и стройте предсказуемую пагинацию.

В процессе Теория: впереди Практика: впереди Тест: впереди
05 / 12
После этой главы
  • Проверяйте query-параметры как недоверенные данные и стройте предсказуемую пагинацию.
  • Применить тему в задаче «Query, пагинация и фильтры» и объяснить граничные случаи.

Около 7 минут на чтение и разбор примеров. Практика — отдельно, в своём темпе.

Query — не готовая бизнес-модель

Query-параметры выражают дополнительные настройки запроса: поисковую строку, страницу, порядок сортировки. Они приходят из URL и являются недоверенным вводом. Конкретный parser может представлять их строками, массивами или более сложной формой, поэтому req.query.page нельзя без проверки считать числом или даже единственной строкой.

Начните с контракта: какие поля разрешены, что означает отсутствие, какие диапазоны допустимы и что делать с повторением одного параметра. Если требуются только простые строки, явно проверяйте typeof. Не вызывайте строковые методы у произвольного значения и не позволяйте настройке parser незаметно определять бизнес-правило приложения.

Значение по умолчанию и полная проверка

Отсутствующая page может означать первую страницу. Но пустая строка или 'abc' — не обязательно то же самое, что отсутствие. Подстановка через || часто объединяет разные ситуации без осознанного решения. Сначала выберите правило отсутствия, затем проверьте форму строки и только после этого преобразуйте её.

JavaScript
function readPage(raw) {
  const value = raw ?? '1';
  if (typeof value !== 'string' || !/^[0-9]+$/.test(value)) return null;
  const page = Number(value);
  return Number.isSafeInteger(page) && page >= 1 && page <= 100
    ? page : null;
}

parseInt('2x',10) вернёт 2, поэтому сам по себе он не доказывает корректность всей строки. Number без предварительной проверки тоже имеет особенности, например пустая строка превращается в ноль. Явная последовательность «форма → преобразование → диапазон» делает правило читаемым и проверяемым.

Offset-пагинация

При нумерации страниц с единицы и фиксированном limit смещение вычисляется как (page - 1) * limit. Для первой страницы offset равен нулю, для второй при limit=10 — десяти. Возвращаемый список и метаданные должны использовать одну систему нумерации. Несогласованность нуля и единицы проявляется как пропущенные или повторяющиеся записи.

JavaScript
app.get('/items', (req, res) => {
  const page = readPage(req.query.page);
  if (page === null) return res.status(400).json({ error: 'page' });
  const limit = 10;
  res.json({ page, limit, offset: (page - 1) * limit });
});

Offset прост и подходит многим небольшим спискам, но на больших изменяющихся наборах имеет ограничения стоимости и стабильности. Записи могут добавляться между запросами страниц. Поэтому важно определить порядок сортировки с устойчивым дополнительным ключом, а не рассчитывать на случайный порядок хранения.

Cursor-пагинация и стабильный порядок

Cursor описывает позицию в согласованном порядке, а не номер страницы. Его формат должен проверяться и не давать клиенту возможность управлять произвольным SQL. Если сортировка использует createdAt, часто нужен ещё уникальный id для однозначного порядка записей с одинаковым временем. Сам факт использования cursor не делает доступ к данным безопасным.

Cursor-подход удобен для ленты, но имеет другой контракт переходов и не всегда позволяет произвольно перейти к странице 57. Выбирайте модель по интерфейсу и характеру данных. Не называйте offset-параметр cursor только потому, что так моднее: клиент должен понимать реальные гарантии и ограничения API.

Фильтрация и сортировка по allowlist

Сортировочное поле, направление и разрешённые фильтры задают закрытым набором. Нельзя напрямую вставлять произвольное имя из URL в текст SQL. В библиотеке работы с БД стройте разрешённую структуру запроса через её параметризованный API. Значения и идентификаторы запросов требуют разных способов обработки.

JavaScript
const allowedSort = new Set(['title', 'createdAt']);
function sortField(raw) {
  return typeof raw === 'string' && allowedSort.has(raw) ? raw : 'createdAt';
}

Ограничивайте длину поисковой строки и максимальный размер страницы. Это одновременно помогает предсказуемости интерфейса и стоимости обработки. Если filter не соответствует допустимой форме, выберите документированный отказ либо значение по умолчанию. Не позволяйте одному endpoint молча игнорировать то, что другой считает ошибкой, без причины и описания.

Практика page от 1 до 100

В задании отсутствие параметра означает 1, а разрешённая строка содержит только цифры и даёт число в диапазоне 1..100. Нужно вернуть page, limit=10 и правильный offset. Неверные значения дают 400 с согласованным JSON. Проверка проводится через HTTP, поэтому req.query действительно проходит путь обработки Express.

Проверьте отсутствие, '2', '0', '101' и '2x'. Не используйте различие между null и строкой 'null' случайно: в solve null обозначает отсутствие query, а переданная строка является настоящим пользовательским значением. Для самостоятельного продолжения добавьте limit с отдельным верхним пределом и тест повторённого query-параметра.

Загружаем редактор…
Следующая темаВалидация HTTP-ввода

Музыка: Reverie — Scott Buckley · CC BY 4.0