---
title: "Створення пошукового ендпоінту Laravel Scout із HTTP-методом QUERY"
url: https://laravelukraine.com/blog/iak-stvoriti-posukovii-endpoint-laravel-scout-z-vikoristanniam-http-query-metodu
date: 2026-07-17
source: https://laravel-news.com/build-a-laravel-scout-search-endpoint-with-the-http-query-method?utm_medium=feed&utm_source=feedpress.me&utm_campaign=Feed%3A+laravelnews
---

# Створення пошукового ендпоінту Laravel Scout із HTTP-методом QUERY

HTTP-метод QUERY - це нове доповнення до специфікації HTTP ([RFC 10008](https://www.rfc-editor.org/rfc/rfc10008)), розроблене саме для одного завдання: запитів. Це безпечний метод із підтримкою кешування, який передає параметри в тілі запиту, надаючи виразність POST-payload без відмови від read-only семантики GET - і без необхідності втискати складні фільтри в URL, який може досягти обмежень довжини.

Laravel 13.19 додав [`Http::query()` до HTTP-клієнта](https://laravel-news.com/laravel-13-19-0) разом із тестовими хелперами `query()` та `queryJson()`. Повноцінний хелпер `Route::query()` [вже злито для Laravel 14](https://github.com/laravel/framework/pull/60655), але чекати не обов'язково: роутер Laravel вже сьогодні приймає користувацькі HTTP-дієслова через `Route::match()`. У цьому tutorial ми використаємо його для побудови невеликого пошукового ендпоінту на основі database-драйвера Laravel Scout.

## Коли потрібен метод QUERY

Один пошуковий рядок, як у нашому прикладі, не обов'язково потребує QUERY - метод починає виправдовувати себе, коли запит розростається до чогось, що GET не може зручно обробити:

- Структуровані фільтри з діапазонами, масивами та групами and/or (пошук продуктів чи оголошень)
- Геопросторові запити, що надсилають координати полігонів ("пошук у цій області карти")
- Пакетні запити, що отримують сотні записів за ID
- Пошук за чутливими значеннями, як-от email або телефони, які не повинні потрапляти в URL, логи доступу або історію браузера
- Ендпоінти звітності з діапазонами дат і group-by, які є читанням, але сьогодні моделюються як `POST /reports/run`

## Налаштування проєкту

Починаючи зі свіжого Laravel 13-додатку, встановіть файл API-маршрутів і Scout:

```
php artisan install:api
composer require laravel/scout
```

Database-драйвер Scout не потребує зовнішнього пошукового сервісу - він використовує `WHERE LIKE` запити до вашої існуючої бази даних, що добре підходить для малих і середніх датасетів. Увімкніть його у файлі `.env`:

```
SCOUT_DRIVER=database
```

Далі створіть модель `Article` з міграцією та фабрикою:

```
php artisan make:model Article -mf
```

Міграція визначає поля `title` та `body`:

```php
Schema::create('articles', function (Blueprint $table) {
    $table->id();
    $table->string('title');
    $table->text('body');
    $table->timestamps();
});
```

Додайте трейт `Searchable` до моделі та визначте, які колонки Scout має шукати:

```php
namespace App\Models;

use Illuminate\Database\Eloquent\Factories\HasFactory;
use Illuminate\Database\Eloquent\Model;
use Laravel\Scout\Searchable;

class Article extends Model
{
    use HasFactory, Searchable;

    protected $fillable = ['title', 'body'];

    public function toSearchableArray(): array
    {
        return [
            'title' => $this->title,
            'body' => $this->body,
        ];
    }
}
```

## Визначення QUERY-маршруту

Роутер Laravel не підтримує `Route::query()` у Laravel 13 (він буде доступний починаючи з Laravel 14). Однак роутер Laravel реєструє маршрути для будь-якого дієслова, яке ви передаєте в `Route::match()`, включно з QUERY.

Додайте наступне до `routes/api.php`:

```php
use App\Models\Article;
use Illuminate\Http\Request;
use Illuminate\Support\Facades\Route;

Route::match(['QUERY'], '/articles/search', function (Request $request) {
    $validated = $request->validate([
        'search' => ['required', 'string'],
        'per_page' => ['sometimes', 'integer', 'between:1,50'],
    ]);

    return Article::search($validated['search'])
        ->paginate($validated['per_page'] ?? 15);
});
```

Зверніть увагу, що валідація та `$request->input()` зчитують дані з JSON-тіла запиту так само, як вони робили б це для POST-запиту - без додаткової роботи. Виконання `php artisan route:list` підтверджує, що маршрут зареєстровано з дієсловом QUERY:

```
QUERY  api/articles/search
```

Запит до цього ендпоінту виглядає як POST із GET-семантикою - критерії пошуку передаються в тілі:

```http
QUERY /api/articles/search HTTP/1.1
Content-Type: application/json
Accept: application/json

{"search": "scout", "per_page": 10}
```

## Тестування ендпоінту

Тестовий хелпер `queryJson()` Laravel 13.19 дзеркалить `postJson()` та подібні методи, тому тестування ендпоінту відчувається знайомо:

```php
namespace Tests\Feature;

use App\Models\Article;
use Illuminate\Foundation\Testing\RefreshDatabase;
use Tests\TestCase;

class ArticleSearchTest extends TestCase
{
    use RefreshDatabase;

    public function test_it_searches_articles_with_the_query_method(): void
    {
        Article::factory()->create(['title' => 'Getting Started With Laravel Scout']);
        Article::factory()->create(['title' => 'Queues in Depth']);

        $this->queryJson('/api/articles/search', ['search' => 'scout'])
            ->assertOk()
            ->assertJsonCount(1, 'data')
            ->assertJsonPath('data.0.title', 'Getting Started With Laravel Scout');
    }

    public function test_it_validates_the_search_term(): void
    {
        $this->queryJson('/api/articles/search', [])
            ->assertUnprocessable()
            ->assertJsonValidationErrors('search');
    }
}
```

На стороні клієнта HTTP-клієнт Laravel може викликати QUERY-ендпоінти методом `Http::query()`, доданим у версії 13.19:

```php
use Illuminate\Support\Facades\Http;

$response = Http::acceptJson()->query('https://example.com/api/articles/search', [
    'search' => 'scout',
]);

$articles = $response->json('data');
```

## Підводний камінь: вбудований сервер PHP

Якщо ви розробляєте з `php artisan serve`, запити з методом QUERY отримують відповідь `501 Not Implemented` ще до того, як потраплять до Laravel - вбудований сервер жорстко кодує методи запитів, які він приймає. Середовища на основі Nginx, як-от Laravel Herd та Valet, передають метод до вашого додатку. Тестові хелпери Laravel HTTP повністю уникають цієї проблеми, оскільки вони відправляють запити безпосередньо через фреймворк.

## Використання QUERY на веб-маршрутах

Наш ендпоінт знаходиться в `routes/api.php`, тому захист CSRF взагалі не виникає. Однак якщо ви зареєструєте QUERY-маршрут у `routes/web.php`, ви отримаєте помилку `419`: middleware `PreventRequestForgery` Laravel 13 трактує лише `GET`, `HEAD` та `OPTIONS` як запити читання, тому QUERY отримує ту саму перевірку токена, що й POST. RFC 10008 визначає QUERY як безпечний метод, і [наступний мажорний реліз Laravel звільняє його відповідно](https://github.com/laravel/framework/pull/60655) - але до того часу у вас є два варіанти.

Перший варіант переносить майбутню поведінку, розширюючи middleware та додаючи QUERY до списку read-дієслів:

```php
namespace App\Http\Middleware;

use Illuminate\Foundation\Http\Middleware\PreventRequestForgery as Middleware;

class PreventRequestForgery extends Middleware
{
    /**
     * Визначити, чи HTTP-запит використовує read-дієслово.
     * QUERY є безпечним методом (RFC 10008), тому трактуємо його як GET.
     * Laravel 14 поставляється з цією поведінкою; видаліть це перевизначення після оновлення.
     */
    protected function isReading($request)
    {
        return in_array($request->method(), ['HEAD', 'GET', 'OPTIONS', 'QUERY']);
    }
}
```

Потім замініть його в групі middleware `web` у `bootstrap/app.php`:

```php
use App\Http\Middleware\PreventRequestForgery;
use Illuminate\Foundation\Http\Middleware\PreventRequestForgery as BasePreventRequestForgery;

->withMiddleware(function (Middleware $middleware): void {
    $middleware->web(replace: [
        BasePreventRequestForgery::class => PreventRequestForgery::class,
    ]);
})
```

Тепер кожен QUERY-маршрут звільнено від CSRF, тоді як POST, PUT, PATCH та DELETE зберігають повний захист, відповідаючи тому, що Laravel 14 робитиме з коробки. Коли ви оновитеся, видаліть клас і рядок `replace`, і нічого більше не зміниться.

Другий варіант виключає один маршрут із middleware:

```php
use Illuminate\Foundation\Http\Middleware\PreventRequestForgery;

Route::match(['QUERY'], '/articles/search', $handler)
    ->withoutMiddleware(PreventRequestForgery::class);
```

Це добре працює для разового ендпоінту, але вам потрібно буде пам'ятати про це на кожному QUERY-маршруті, який ви додаєте, і це повністю видаляє middleware з маршруту, а не класифікує дієслово як читання. Оскільки перевизначення middleware - це зміна двох файлів, яка чисто зникає після оновлення, ми б обрали його першим.

Одна примітка щодо перевірки будь-якого підходу: CSRF middleware пропускає перевірку, коли середовище додатка - `testing`, тому feature-тест не отримає 419, якщо ви спочатку не переназначите середовище (наприклад, `$this->app['env'] = 'production';` у методі `setUp()` вашого тесту).

Нарешті, пам'ятайте, що означає звільнення: фреймворк довіряє, що ваші QUERY-обробники є read-only, так само як він довіряє вашим GET-обробникам. Не змінюйте стан у QUERY-маршруті.

## Перед відправкою QUERY у production

Laravel добре обробляє QUERY - маршрути навіть працюють із `php artisan route:cache` - але фреймворк - це лише один крок у ланцюгу. Кілька речей, які потрібно перевірити перед використанням методу в production API:

- **Кожен проміжний сервіс має пропускати метод.** Ми вже бачили, як вбудований сервер PHP повертає 501; CDN, WAF та load balancer можуть бути так само категоричними щодо дієслів, які вони не розпізнають. Тестуйте повний шлях від клієнта до додатка, а не лише додаток.
- **Браузерні виклики мають застереження.** `fetch()` надсилає QUERY без проблем, але HTML-форми говорять лише GET та POST, і QUERY не є CORS-safelisted методом - кожен cross-origin запит викличе preflight.
- **Переваги кешування поки що на папері.** RFC визначає QUERY як cacheable, але браузери та CDN ще не реалізують кешування QUERY, тому не приймайте метод лише через цю перевагу сьогодні.
- **Інструменти специфікацій наздоганяють.** OpenAPI отримав повноцінну операцію `query` лише у [версії 3.2](https://www.openapis.org/blog/2025/09/23/announcing-openapi-v3-2), випущеній у вересні 2025 року - генератори та SDK-ланцюжки інструментів, які все ще орієнтуються на 3.0 або 3.1, не можуть описати ендпоінт, а деякі HTTP-клієнтські бібліотеки жорстко кодують дієслова, які вони приймають.

Жодне з цього не є dealbreaker для внутрішнього API, де ви контролюєте обидва кінці - що також є найпростішим місцем для першої спроби QUERY.

## Що далі

Коли вийде наступна мажорна версія Laravel, виклик `Route::match(['QUERY'], ...)` може стати `Route::query('/articles/search', ...)`, а перевизначення CSRF вище можна видалити - решта коду в цьому tutorial залишається незмінною.

Між маршрутизацією, HTTP-клієнтом і тестовими хелперами Laravel покриває обидва кінці методу QUERY сьогодні. Ви можете дізнатися більше про метод QUERY у [RFC 10008](https://www.rfc-editor.org/rfc/rfc10008) та слідкувати за підтримкою маршрутизації в [Pull Request #60655](https://github.com/laravel/framework/pull/60655).
