Query String в REST API: проектирование фильтрации, сортировки, пагинации и выборки полей

Превратите хаос URL в порядок! Учимся фильтровать, сортировать и пагинировать данные как профи, не ломая эндпоинты.

Бесплатная консультация юриста

+7 (800) 301-07-30

Реклама jurik.ru

В современной веб-разработке Query String (строка запроса) — это неотъемлемая часть архитектуры REST API. Именно она позволяет клиентам фильтровать, сортировать, пагинировать и формировать представление ресурсов без изменения структуры URL-пути (endpoint). Грамотное проектирование параметров запроса — залог удобного, предсказуемого и масштабируемого API.


Что такое Query String и где он находится?

Query String — это часть URL, которая идет после знака вопроса (?) и содержит пары «ключ-значение», разделенные амперсандом (&).

GET /api/v1/users?status=active&role=admin&page=2&limit=20
 ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
 Это Query String
  • Путь (Path): /api/v1/users — идентифицирует коллекцию ресурсов.
  • Строка запроса (Query String): ?status=active&role=admin... — уточняет, какие именно ресурсы и в каком виде мы хотим получить.

Важно: Согласно спецификации HTTP, длина URL (включая Query String) может быть ограничена браузерами и серверами (обычно ~2000-). Для сложных фильтров с большим объемом данных лучше использовать POST с телом запроса (pattern «Query by Example»), но для 95% задач GET + Query String — стандарт де-факто.


Основные сценарии использования (Паттерны)

Фильтрация (Filtering)

Позволяет сузить выборку ресурсов. Ключи параметров обычно соответствуют именам полей модели данных.

  • Точное совпадение: ?status=published&author_id=42
  • Множественные значения (OR-логика): ?status=published&status=draft (повтор ключа) или ?status=published,draft (через запятую — требует документации).
  • Операторы сравнения (суффиксы): Для чисел, дат и строк часто используют суффиксы в ключе:
    • ?price[gte]=100&price[lte]=500 (Greater Than or Equal / Less Than or Equal)
    • ?created_at[gt]=2023-01-01
    • ?name[like]=ivan (частичное совпадение)

Сортировка (Sorting)

Стандартный параметр — sort или order_by.

  • По одному полю: ?sort=created_at (по возрастанию, ASC).
  • По убыванию (DESC): Префикс минус ?sort=-created_at или ?sort=created_at:desc.
  • Множественная сортировка: ?sort=-status,created_at (сначала по статусу убыв., потом по дате возр.).

Пагинация (Pagination)

Необходима для производительности и UX. Существует два основных подхода:

  1. Offset-based (Классическая): ?page=2&limit=20 или ?offset=20&limit=20.
    • Плюсы: Простая реализация, легко переходить на конкретную страницу.
    • Минусы: Проблемы с производительностью на больших смещениях (OFFSET 100000), «дрейф» данных при вставке/удалении записей во время листвания.
  2. Cursor-based (Keyset Pagination): ?cursor=eyJpZCI6MTAwfQ==&limit=20.
    • Плюсы: Стабильная производительность, нет дрейфа данных, идеально для бесконечной прокрутки (infinite scroll).
    • Минусы: Нельзя перепрыгнуть на страницу N, сложнее реализация (курсор, это обычно закодированный ID/сортировочный ключ последней записи).

Выборка полей (Field Selection / Projection)

Позволяет клиенту запрашивать только нужные данные, экономя трафик и память.

  • ?fields=id,name,email,created_at
  • В ответе сервер возвращает объекты только с этими полями.

Вложенные ресурсы (Embedding / Expanding)

Аналог JOIN в SQL. Позволяет загрузить связанные сущности в одном запросе (решение проблемы N+1).

  • ?include=posts,comments.author
  • Стандарт JSON:API рекомендует параметр include.

Поиск (Search)

Для полнотекстового или нечеткого поиска часто выделяют отдельный параметр.

  • ?q=iphone 15 pro (стандарт q или search)
  • ?search=name:ivan status:active (продвинутый синтаксис типа Lucene/Elasticsearch).

Стандартизация и Best Practices

Именование параметров

  • Используйте snake_case (user_id, created_at) — это стандарт де-факто в веб-API (JSON также snake_case).
  • Избегайте зарезервированных слов HTTP/URI (format, callback, method), если они не несут специального смысла для вашего фреймворка.
  • Имена должны быть самоописательными: ?min_price=100 лучше, чем ?min=100.

Кодирование (URL Encoding / Percent Encoding)

Невероятно важно помнить: Любые значения, содержащие спецсимволы (пробелы, кириллица, &, =, ?, #, %), обязательно должны быть закодированы.

Исходное значение Закодированное (URL Encoded)
поиск по сайту %D0%BF%D0%BE%D0%B8%D1%81%D0%BA%20%D0%BF%D0%BE%20%D1%81%D0%B0%D0%B9%D1%82%D1%83
price > 100 price%20%3E%20100
a&b=c a%26b%3Dc

Всегда используйте встроенные функции языка/фреймворка для построения URL (URLSearchParams в JS, urllib.parse.urlencode в Python, http_build_query в PHP). Никогда не конкатенируйте строки вручную.

Обработка массивов и сложных объектов

В стандарте RFC 3986 нет строгого правила для массивов. Популярные соглашения:

  1. Повтор ключа (PHP/Node.js/Express default, Rails): ?ids=1&ids=2&ids=3 → ['1', '2', '3'].
  2. Квадратные скобки (PHP legacy, some frameworks): ?ids[]=1&ids[]=2.
  3. Запятая в значении: ?ids=1,2,3 (компактно, но требует парсинга на сервере и экранирования запятых внутри значений).
  4. Индексы (редко): ?ids[0]=1&ids[1]=2.

Рекомендация: Выберите один стиль для всего API и задокументируйте его. Повтор ключа — самый совместимый с веб-стандартами вариант.

Значения по умолчанию (Defaults)

API должно работать предсказуемо без явных параметров.

  • GET /users ≡ GET /users?page=1&limit=20&sort=-created_at.
  • Документируйте дефолты в OpenAPI/Swagger.
  • Ограничивайте limit максимумом (например, max_limit=100), чтобы клиент не «уронил» БД запросом ?limit=1000000.

Пример «Идеального» запроса

Собираем всё вместе: получаем 2-ю страницу активных пользователей с ролью админ или модератор, сортируем по дате регистрации (новые первые), берем только id и email, и подгружаем профили.

GET /api/v1/users
 ?status=active
 &role=admin,moderator
 &page=2
 &limit=25
 &sort=-registered_at
 &fields=id,email,registered_at
 &include=profile

Безопасность и Валидация

  • Валидируйте всё: Типы (integer, string, enum), диапазоны (limit <= 100, page > 0), белые списки полей для sort и fields (защита от извлечения скрытых колонок БД).
  • SQL Injection / NoSQL Injection: Никогда не подставляйте значения из Query String прямо в запрос к БД. Используйте ORM / Query Builder / Параметризованные запросы.
  • DoS через пагинацию: Жестко лимитируйте limit и offset (или используйте Cursor-based).
  • Information Leakage: Не возвращайте ошибки БД (названия колонок, типы ошибок) в ответе 400/500. Возвращайте понятные сообщения: "Invalid sort field: 'password_hash'. Allowed: id, name, email".

Документирование (OpenAPI 3.0 / Swagger)

Чтобы API было удобно потреблять, параметры Query String должны быть описаны в спецификации.

parameters:
 ⎼ name: status
 in: query
 description: Фильтр по статусу пользователя
 schema:
 type: string
 enum: [active, inactive, banned]
 default: active
 ⎼ name: role
 in: query
 description: Фильтр по ролям (через запятую ‒ OR логика)
 schema:
 type: array
 items:
 type: string
 enum: [admin, user, moderator]
 style: form
 explode: false # role=admin,user (если true: role=admin&role=user)
 ‒ name: page
 in: query
 schema:
 type: integer
 minimum: 1
 default: 1
 ‒ name: limit
 in: query
 schema:
 type: integer
 minimum: 1
 maximum: 100
 default: 20

Ключевые поля style и explode в OpenAPI 3.0 точно описывают, как сериализовать массивы и объекты (form, spaceDelimited, pipeDelimited, deepObject).


Чек-лист для Code Review API

  1. [ ] Все параметры валидируются на входе (Whitelist подход).
  2. [ ] Используется единый стиль именования (snake_case).
  3. [ ] Пагинация имеет дефолтные значения и жесткий максимум limit.
  4. [ ] Сортировка разрешена только по безопасным/индексируемым колонкам.
  5. [ ] Поддерживаеться выборка полей (fields) для экономии трафика.
  6. [ ] Сложные фильтры документированы (операторы [gte], [like] и т.д.).
  7. [ ] Ошибки валидации возвращают 400 Bad Request с понятным JSON-телом (например, формат RFC 7807 / Problem Details).
  8. [ ] Query String корректно кодируется/декодируется на клиенте и сервере.
  9. [ ] API задокументировано в OpenAPI/Swagger с примерами.

Query String — это язык диалога между клиентом и сервером при чтении данных. Главное правило: проектируйте его так, будто вы сами будете писать клиентский код. Единообразие, предсказуемость, строгая валидация и подробная документация — три кита, на которых держится удобное REST API. Не изобретайте велосипеды: используйте проверенные паттерны (JSON:API, Google API Improvement Proposals) и стандартные библиотеки для парсинга и генерации URL.