В современной веб-разработке 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. Существует два основных подхода:
- Offset-based (Классическая):
?page=2&limit=20или?offset=20&limit=20.- Плюсы: Простая реализация, легко переходить на конкретную страницу.
- Минусы: Проблемы с производительностью на больших смещениях (OFFSET 100000), «дрейф» данных при вставке/удалении записей во время листвания.
- 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 нет строгого правила для массивов. Популярные соглашения:
- Повтор ключа (PHP/Node.js/Express default, Rails):
?ids=1&ids=2&ids=3→['1', '2', '3']. - Квадратные скобки (PHP legacy, some frameworks):
?ids[]=1&ids[]=2. - Запятая в значении:
?ids=1,2,3(компактно, но требует парсинга на сервере и экранирования запятых внутри значений). - Индексы (редко):
?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
- [ ] Все параметры валидируются на входе (Whitelist подход).
- [ ] Используется единый стиль именования (snake_case).
- [ ] Пагинация имеет дефолтные значения и жесткий максимум
limit. - [ ] Сортировка разрешена только по безопасным/индексируемым колонкам.
- [ ] Поддерживаеться выборка полей (
fields) для экономии трафика. - [ ] Сложные фильтры документированы (операторы
[gte],[like]и т.д.). - [ ] Ошибки валидации возвращают
400 Bad Requestс понятным JSON-телом (например, формат RFC 7807 / Problem Details). - [ ] Query String корректно кодируется/декодируется на клиенте и сервере.
- [ ] API задокументировано в OpenAPI/Swagger с примерами.
Query String — это язык диалога между клиентом и сервером при чтении данных. Главное правило: проектируйте его так, будто вы сами будете писать клиентский код. Единообразие, предсказуемость, строгая валидация и подробная документация — три кита, на которых держится удобное REST API. Не изобретайте велосипеды: используйте проверенные паттерны (JSON:API, Google API Improvement Proposals) и стандартные библиотеки для парсинга и генерации URL.