Списки в API майже завжди потребують фільтрів, сортування й обмеження полів. Головне - однакова схема для всіх ендпойнтів, щоб клієнтам не доводилося вчити кожен окремо.
Поширена схема (близька до JSON:API):
GET /api/vacancies?filter[city]=kyiv&filter[remote]=1&filter[salary_from]=2000
&sort=-published_at,title
&fields[vacancies]=id,title,salary
&include=company
&page[size]=20
filter[...]- умови; для діапазонів - суфікси (salary_from/salary_to) або оператори (filter[salary][gte]=2000);sort- список полів через кому,-- за спаданням;fields[тип]- лише потрібні поля (sparse fieldsets): мобільному списку не треба повного опису вакансії;include- пов'язані ресурси в тій самій відповіді.
У Laravel розбір таких параметрів дає spatie/laravel-query-builder:
QueryBuilder::for(Vacancy::class)
->allowedFilters(['city', AllowedFilter::exact('remote'), AllowedFilter::scope('salary_from')])
->allowedSorts(['published_at', 'title'])
->allowedFields(['id', 'title', 'salary'])
->allowedIncludes(['company'])
->paginate();
А вбудовані JSON:API-ресурси Laravel (JsonApiResource) самі обробляють fields і include у відповіді.
Безпека й продуктивність - головне:
- білий список полів для фільтрів, сортування й
include. Сортування за довільною колонкою з параметра - це і SQL-ризики, і повільні запити за полями без індексу. Невідомий параметр - помилка 400, а не тихе ігнорування; - індекси під реальні фільтри: кожна дозволена комбінація фільтр + сортування - потенційний запит, і для частих комбінацій потрібні складені індекси;
- обмеження глибини
includeі кількості елементів - інакше один запит витягне половину бази; - приховані поля:
fieldsне повинен відкривати поля, яких немає в звичайній відповіді (password_hash, внутрішні примітки).
Пошук за текстом - окремий параметр (filter[q]=laravel чи q=), який веде в повнотекстовий пошук (Scout, Meilisearch), а не в LIKE '%...%' по кількох колонках.
Документування: кожен дозволений фільтр і сортування мають бути в OpenAPI-описі - інакше клієнти вгадують.