Query-параметры — данные, которые едут в самом URL после знака вопроса:
https://jsonplaceholder.typicode.com/posts?userId=1&_limit=5
Пары «ключ=значение», разделённые амперсандом. Пара после первого ?, остальные через &. Это самый частый способ уточнить GET-запрос — чего именно вы хотите.
Зачем
В классическом REST URL содержит существительное-ресурс: /posts. Но «все посты» — это часто слишком много. Нужно отфильтровать, ограничить, отсортировать. Для этого и нужны query-параметры:
- фильтр: ?status=published&category=css;
- сортировка: ?sort=created_at&order=desc;
- пагинация: ?page=2&per_page=20;
- поиск: ?q=fetch&in=title;
- выбор полей: ?fields=id,title,author.
Конкретные имена параметров каждый API выбирает сам. Никакого единого стандарта нет, но шаблоны выше встречаются повсюду.
Простой случай: руками склеить строку
const userId = 1;
const limit = 5;
const url = `https://jsonplaceholder.typicode.com/posts?userId=${userId}&_limit=${limit}`;
// _limit - не баг. В JSONPlaceholder параметры для пагинации и сортировки специально идут с нижним подчёркиванием
const response = await fetch(url);
const posts = await response.json();
Работает. Но если значения содержат пробелы, кириллицу, амперсанды, плюсы, знаки вопроса — пойдут проблемы. Любой такой символ нужно экранировать (заменить на %XX-последовательность). Делать это вручную — больно и легко забыть.
Правильный способ: URLSearchParams
В каждом современном браузере есть встроенный объект URLSearchParams, который собирает строку query-параметров правильно:
const params = new URLSearchParams({
q: 'fetch & promise',
category: 'css',
page: 2,
});
const url = `https://api.example.com/search?${params.toString()}`;
// → https://api.example.com/search?q=fetch+%26+promise&category=css&page=2
const response = await fetch(url);
Все спецсимволы экранируются автоматически. Получившаяся строка валидна, никаких сюрпризов сервер не словит.
Если значение пустое или undefined — параметр всё равно добавится с пустым значением. Если этого не хочется, фильтруем заранее:
const filters = { q: searchInput, category: selectedCategory };
const params = new URLSearchParams();
for (const [key, value] of Object.entries(filters)) {
if (value) params.append(key, value);
}
Несколько значений одного параметра
Бывает, нужно передать массив значений. Универсального стандарта нет, чаще всего работает повтор ключа:
const params = new URLSearchParams();
params.append('tag', 'css');
params.append('tag', 'html');
params.append('tag', 'js');
// → tag=css&tag=html&tag=js
Некоторые API ждут другой формат — tag[]=css&tag[]=html или tags=css,html,js. Смотрите документацию.
Пагинация: три популярных шаблона
На больших коллекциях сервер не отдаёт всё разом, а отдаёт «страницами». Три самых частых способа.
1. limit + offset:
GET /posts?limit=20&offset=40 ← вернуть 20 записей, начиная с 41-й
2. page + per_page:
GET /posts?page=3&per_page=20 ← то же самое, но в номерах страниц
3. Cursor-based:
GET /posts?after=eyJpZCI6MTIzfQ&limit=20 ← вернуть 20 записей после курсора
Третий способ — самый правильный для больших и часто меняющихся коллекций. Если между запросами добавляются новые записи, limit/offset начинает повторять элементы или пропускать. Cursor этого недостатка лишён, потому что «закладка» привязана не к номеру строки, а к конкретной записи.
На собеседовании могут попросить рассказать разницу между offset и cursor — пригодится.
Ограничения
Query-параметры удобны, но не подходят для:
- Большого объёма данных. Браузеры ограничивают длину URL примерно 2 000–8 000 символов. Если нужно отправить килобайт JSON — кладите в тело POST-запроса.
- Чувствительных данных. URL попадает в логи серверов, истории браузера, заголовок Referer. Пароли, токены и другие секреты — никогда в query.
- Действий, меняющих данные. GET — safe-метод (см. главу 3.2). Удаление, создание, обновление через query-параметры на GET-эндпоинт — антипаттерн.
Для этих случаев используем тело запроса — следующая глава.