> ## Documentation Index
> Fetch the complete documentation index at: https://kawax.biz/llms.txt
> Use this file to discover all available pages before exploring further.

# HTTP-client

> Leer hoe je met de HTTP-client van Laravel requests naar externe API's stuurt. Behandelt authenticatie, time-outs, retries en testen.

## Wat is de HTTP-client

De HTTP-client van Laravel is een gebruiksvriendelijke API die [Guzzle](https://docs.guzzlephp.org/en/stable/) omhult.
Via de `Http`-facade schrijf je beknopt HTTP-requests naar externe webservices en API's.

```php theme={null}
use Illuminate\Support\Facades\Http;

$response = Http::get('https://api.example.com/users');
```

<Info>
  Guzzle is al vooraf geïnstalleerd, dus je kunt zonder extra configuratie direct aan de slag.
</Info>

## Basisrequests

### GET-requests

```php theme={null}
use Illuminate\Support\Facades\Http;

$response = Http::get('https://api.example.com/users');
```

Queryparameters kun je als array doorgeven.

```php theme={null}
$response = Http::get('https://api.example.com/users', [
    'page' => 1,
    'per_page' => 20,
]);
```

### POST-requests

Data wordt standaard verzonden als `application/json`.

```php theme={null}
$response = Http::post('https://api.example.com/users', [
    'name' => 'Taro Yamada',
    'email' => 'taro@example.com',
]);
```

### PUT / PATCH / DELETE

```php theme={null}
// PUT-request
$response = Http::put('https://api.example.com/users/1', [
    'name' => 'Hanako Yamada',
]);

// PATCH-request
$response = Http::patch('https://api.example.com/users/1', [
    'email' => 'hanako@example.com',
]);

// DELETE-request
$response = Http::delete('https://api.example.com/users/1');
```

## Responses verwerken

Methoden zoals `Http::get()` geven een `Illuminate\Http\Client\Response`-instantie terug.
Dit object biedt een groot aantal methoden om de response te inspecteren.

```php theme={null}
$response = Http::get('https://api.example.com/users/1');

// Responsebody
$response->body();        // Als string ophalen
$response->json();        // Omzetten naar een array
$response->json('name');  // Een specifieke JSON-sleutel ophalen
$response->object();      // Als stdClass-object ophalen
$response->collect();     // Als Collection ophalen

// Status
$response->status();      // Statuscode (bijv. 200)
$response->successful();  // true bij 2xx
$response->failed();      // true bij 4xx of hoger
$response->clientError(); // true bij 4xx
$response->serverError(); // true bij 5xx

// Veelgebruikte statuscodecontroles
$response->ok();           // 200
$response->created();      // 201
$response->noContent();    // 204
$response->notFound();     // 404
$response->unauthorized(); // 401
$response->forbidden();    // 403
$response->unprocessableEntity(); // 422
$response->tooManyRequests();     // 429
```

JSON-responses kun je ook via array-toegang uitlezen.

```php theme={null}
$name = Http::get('https://api.example.com/users/1')['name'];
```

### JSON-decodeeropties

Aan het tweede argument van `json()` kun je JSON-decodeervlaggen doorgeven. Wil je ongeldige JSON als exceptie behandelen, geef dan PHP's `JSON_THROW_ON_ERROR` op.

```php theme={null}
use JsonException;
use Illuminate\Support\Facades\Http;

try {
    $data = Http::get('https://api.example.com/users/1')
        ->json(flags: JSON_THROW_ON_ERROR);
} catch (JsonException $e) {
    report($e);
}
```

Ook wanneer je in het eerste argument een sleutel opgeeft, kun je in het tweede argument vlaggen doorgeven.

```php theme={null}
$name = Http::get('https://api.example.com/users/1')
    ->json('name', JSON_THROW_ON_ERROR);
```

## Requestopties

### Headers instellen

```php theme={null}
$response = Http::withHeaders([
    'X-Api-Version' => '2',
    'Accept-Language' => 'ja',
])->get('https://api.example.com/users');
```

Om aan te geven dat je `application/json` accepteert is `acceptJson()` handig.

```php theme={null}
$response = Http::acceptJson()->get('https://api.example.com/users');
```

<Info>
  Voor de CSRF-headers (`X-CSRF-TOKEN` / `X-XSRF-TOKEN`) van AJAX-requests die je vanuit de browser naar Laravel stuurt, zie [CSRF-beveiliging](/nl/csrf).
</Info>

### Authenticatie

**Bearer-tokenauthenticatie** (het meest gebruikelijk):

```php theme={null}
$response = Http::withToken($token)->get('https://api.example.com/me');
```

**Basic-authenticatie**:

```php theme={null}
$response = Http::withBasicAuth('user@example.com', 'password')
    ->get('https://api.example.com/private');
```

### Een basis-URL instellen

Stuur je veel requests naar dezelfde host, dan kun je die bundelen met `baseUrl()`.

```php theme={null}
$response = Http::baseUrl('https://api.example.com')
    ->withToken($token)
    ->get('/users/1');
```

### Formulierdata versturen

Wil je verzenden als `application/x-www-form-urlencoded`, gebruik dan `asForm()`.

```php theme={null}
$response = Http::asForm()->post('https://api.example.com/login', [
    'username' => 'taro',
    'password' => 'secret',
]);
```

### Time-outs

```php theme={null}
// Response-time-out (standaard 30 seconden)
$response = Http::timeout(10)->get('https://api.example.com/slow-endpoint');

// Verbindingstime-out (standaard 10 seconden)
$response = Http::connectTimeout(5)->get('https://api.example.com/endpoint');
```

<Warning>
  Wordt de time-out overschreden, dan wordt een `Illuminate\Http\Client\ConnectionException` gegooid.
  Het is aan te raden om bij aanroepen van externe API's altijd een time-out in te stellen.
</Warning>

### Retries

Voor tijdelijke netwerkstoringen of serverfouten kun je automatische retries instellen.

```php theme={null}
// Maximaal 3 keer opnieuw proberen met tussenpozen van 100 milliseconden
$response = Http::retry(3, 100)->post('https://api.example.com/orders', $data);
```

Voorwaardelijke retries (bijvoorbeeld alleen opnieuw proberen bij verbindingsfouten):

```php theme={null}
use Illuminate\Http\Client\PendingRequest;
use Throwable;
use Illuminate\Http\Client\ConnectionException;

$response = Http::retry(3, 100, function (Throwable $exception, PendingRequest $request) {
    return $exception instanceof ConnectionException;
})->post('https://api.example.com/orders', $data);
```

## Foutafhandeling

### Fouten handmatig controleren

De HTTP-client van Laravel gooit standaard geen excepties bij 4xx- en 5xx-responses.
Je controleert expliciet met `failed()`, `clientError()` en dergelijke.

```php theme={null}
$response = Http::get('https://api.example.com/users/999');

if ($response->notFound()) {
    // Afhandeling van 404
}

if ($response->failed()) {
    // Bijvoorbeeld een foutlog vastleggen
    logger()->error('API request failed', ['status' => $response->status()]);
}
```

### Excepties gooien

Met `throw()` wordt bij een fout een `Illuminate\Http\Client\RequestException` gegooid.

```php theme={null}
// Bij een foutresponse (4xx/5xx) een exceptie gooien
$response = Http::post('https://api.example.com/users', $data)->throw();

// throwIf(): gooit wanneer de voorwaarde true is (onderstaand is een losstaand voorbeeld)
$response = Http::get('https://api.example.com/users/1');
$response->throwIf($response->status() === 422);

// throwUnlessStatus(): gooit bij elke andere statuscode dan de opgegeven (ook een losstaand voorbeeld)
$response = Http::post('https://api.example.com/orders', $data);
$response->throwUnlessStatus(201);
```

`throw()` geeft de responsinstantie terug, dus je kunt het in een methodchain gebruiken.

```php theme={null}
$user = Http::post('https://api.example.com/users', $data)
    ->throw()
    ->json();
```

Wanneer je de exceptie opvangt en afhandelt:

```php theme={null}
use Illuminate\Http\Client\RequestException;
use Illuminate\Http\Client\ConnectionException;

try {
    $response = Http::timeout(5)
        ->post('https://api.example.com/users', $data)
        ->throw();
} catch (ConnectionException $e) {
    // Time-out of verbindingsfout
    logger()->error('Connection failed: ' . $e->getMessage());
} catch (RequestException $e) {
    // 4xx- / 5xx-fout
    logger()->error('API error', ['status' => $e->response->status()]);
}
```

## Gelijktijdige requests

Wil je meerdere API's tegelijk aanroepen, dan kun je ze parallel uitvoeren met `pool()`.

```php theme={null}
use Illuminate\Http\Client\Pool;
use Illuminate\Support\Facades\Http;

$responses = Http::pool(fn (Pool $pool) => [
    $pool->as('users')->get('https://api.example.com/users'),
    $pool->as('posts')->get('https://api.example.com/posts'),
    $pool->as('comments')->get('https://api.example.com/comments'),
]);

$users    = $responses['users']->json();
$posts    = $responses['posts']->json();
$comments = $responses['comments']->json();
```

Faalt een request in de pool op verbindingsniveau, bijvoorbeeld door een time-out of DNS-fout, dan is het betreffende element in `$responses` geen `Response` maar een instantie van `Illuminate\Http\Client\ConnectionException`.

```php theme={null}
foreach ($responses as $response) {
    if ($response instanceof Throwable) {
        // De verbinding is mislukt...
    } elseif ($response->failed()) {
        // De verbinding is gelukt, maar er is een foutresponse ontvangen...
    }
}
```

<Tip>
  Dit is aanzienlijk sneller dan de requests één voor één uitvoeren. Handig voor bijvoorbeeld dashboards die meerdere externe API's aanroepen.
</Tip>

## Testen

### Mocken met Http::fake()

In tests gebruik je `Http::fake()` om responses te simuleren zonder daadwerkelijk HTTP-requests te versturen.

```php theme={null}
use Illuminate\Support\Facades\Http;

Http::fake();

// Geeft 200 terug voor alle requests
$response = Http::get('https://api.example.com/users');
$response->successful(); // true
```

Een response instellen voor specifieke URL's:

```php theme={null}
Http::fake([
    'api.example.com/users/*' => Http::response(['id' => 1, 'name' => 'Taro Yamada'], 200),
    'api.example.com/posts/*' => Http::response(['error' => 'Not Found'], 404),
    '*' => Http::response('OK', 200),  // Al het overige
]);
```

Een responsesequentie (bij herhaalde aanroepen worden responses op volgorde teruggegeven):

```php theme={null}
Http::fake([
    // 1e keer: 200, 2e keer: 200, 3e keer: 429
    // Is de sequentie op, dan gooien volgende requests een exceptie
    'api.example.com/*' => Http::sequence()
        ->push(['id' => 1], 200)
        ->push(['id' => 2], 200)
        ->pushStatus(429),
]);
```

### Requests verifiëren

Met `Http::assertSent()` verifieer je de inhoud van requests.

```php theme={null}
use Illuminate\Http\Client\Request;
use Illuminate\Support\Facades\Http;

Http::fake();

Http::withToken('my-token')->post('https://api.example.com/users', [
    'name' => 'Taro Yamada',
]);

Http::assertSent(function (Request $request) {
    return $request->url() === 'https://api.example.com/users'
        && $request->hasHeader('Authorization', 'Bearer my-token')
        && $request['name'] === 'Taro Yamada';
});

// Controleren dat iets niet is verzonden
Http::assertNotSent(function (Request $request) {
    return $request->url() === 'https://api.example.com/admin';
});

// Controleren dat er precies één request is verzonden
Http::assertSentCount(1);
```

<Tip>
  Roep in tests altijd eerst `Http::fake()` aan.
  Vergeet je dat, dan gaan er echte requests naar de externe API.
  Met `Http::preventStrayRequests()` kun je een exceptie laten gooien bij requests naar URL's die niet zijn gefaket.
</Tip>

### Stray requests voorkomen

```php theme={null}
Http::preventStrayRequests();

Http::fake([
    'api.example.com/*' => Http::response(['ok' => true]),
]);

// Requests naar niet-gefakete URL's leveren een exceptie op
Http::get('https://other.example.com/endpoint'); // Gooit een exceptie
```

## Praktijkvoorbeeld: een serviceklasse die een externe API aanroept

In echte projecten is het een best practice om de HTTP-clientlogica te bundelen in een serviceklasse.

<Steps>
  <Step title="Maak de serviceklasse">
    ```php theme={null}
    <?php

    namespace App\Services;

    use Illuminate\Http\Client\RequestException;
    use Illuminate\Http\Client\ConnectionException;
    use Illuminate\Support\Facades\Http;

    class GitHubService
    {
        private string $baseUrl = 'https://api.github.com';

        public function __construct(
            private readonly string $token,
        ) {}

        /**
         * Gebruikersinformatie ophalen
         *
         * @throws ConnectionException
         * @throws RequestException
         */
        public function getUser(string $username): array
        {
            return Http::baseUrl($this->baseUrl)
                ->withToken($this->token)
                ->acceptJson()
                ->timeout(10)
                ->get("/users/{$username}")
                ->throw()
                ->json();
        }

        /**
         * De lijst met repositories ophalen
         *
         * @throws ConnectionException
         * @throws RequestException
         */
        public function getRepositories(string $username, int $page = 1): array
        {
            return Http::baseUrl($this->baseUrl)
                ->withToken($this->token)
                ->acceptJson()
                ->timeout(10)
                ->retry(2, 500)
                ->get("/users/{$username}/repos", [
                    'page' => $page,
                    'per_page' => 30,
                    'sort' => 'updated',
                ])
                ->throw()
                ->json();
        }
    }
    ```
  </Step>

  <Step title="Registreer in een serviceprovider">
    ```php theme={null}
    // app/Providers/AppServiceProvider.php

    use App\Services\GitHubService;

    public function register(): void
    {
        $this->app->singleton(GitHubService::class, function () {
            return new GitHubService(
                token: config('services.github.token'),
            );
        });
    }
    ```

    ```php theme={null}
    // config/services.php
    'github' => [
        'token' => env('GITHUB_TOKEN'),
    ],
    ```
  </Step>

  <Step title="Gebruik vanuit een controller">
    ```php theme={null}
    <?php

    namespace App\Http\Controllers;

    use App\Services\GitHubService;
    use Illuminate\Http\Client\RequestException;
    use Illuminate\Http\Client\ConnectionException;
    use Illuminate\Http\JsonResponse;

    class GitHubController extends Controller
    {
        public function __construct(
            private readonly GitHubService $github,
        ) {}

        public function show(string $username): JsonResponse
        {
            try {
                $user = $this->github->getUser($username);
                return response()->json($user);
            } catch (ConnectionException) {
                return response()->json(['error' => 'Kon geen verbinding maken met de GitHub API.'], 503);
            } catch (RequestException $e) {
                $status = $e->response->status();
                if ($status === 404) {
                    return response()->json(['error' => 'Gebruiker niet gevonden.'], 404);
                }
                return response()->json(['error' => 'Er is een fout opgetreden in de GitHub API.'], 502);
            }
        }
    }
    ```
  </Step>

  <Step title="Schrijf tests">
    ```php theme={null}
    <?php

    namespace Tests\Unit\Services;

    use App\Services\GitHubService;
    use Illuminate\Http\Client\ConnectionException;
    use Illuminate\Http\Client\RequestException;
    use Illuminate\Support\Facades\Http;
    use Tests\TestCase;

    class GitHubServiceTest extends TestCase
    {
        private GitHubService $service;

        protected function setUp(): void
        {
            parent::setUp();

            Http::fake([
                'api.github.com/users/octocat' => Http::response([
                    'login' => 'octocat',
                    'name' => 'The Octocat',
                    'public_repos' => 8,
                ], 200),
                'api.github.com/users/notfound' => Http::response(
                    ['message' => 'Not Found'],
                    404
                ),
            ]);

            $this->service = new GitHubService(token: 'test-token');
        }

        public function test_gebruikersinformatie_kan_worden_opgehaald(): void
        {
            $user = $this->service->getUser('octocat');

            $this->assertEquals('octocat', $user['login']);
            $this->assertEquals('The Octocat', $user['name']);

            Http::assertSent(function ($request) {
                return $request->url() === 'https://api.github.com/users/octocat'
                    && $request->hasHeader('Authorization', 'Bearer test-token');
            });
        }

        public function test_niet_bestaande_gebruiker_gooit_exceptie(): void
        {
            $this->expectException(RequestException::class);

            $this->service->getUser('notfound');
        }
    }
    ```
  </Step>
</Steps>

## Samenvatting

<AccordionGroup>
  <Accordion title="Overzicht van veelgebruikte methoden">
    | Methode                    | Doel                       |
    | -------------------------- | -------------------------- |
    | `Http::get($url, $query)`  | GET-request                |
    | `Http::post($url, $data)`  | POST-request (JSON)        |
    | `Http::put($url, $data)`   | PUT-request                |
    | `Http::patch($url, $data)` | PATCH-request              |
    | `Http::delete($url)`       | DELETE-request             |
    | `->withToken($token)`      | Bearer-authenticatie       |
    | `->withHeaders($headers)`  | Aangepaste headers         |
    | `->timeout($seconds)`      | Time-out instellen         |
    | `->retry($times, $sleep)`  | Automatische retries       |
    | `->throw()`                | Exceptie gooien bij fouten |
    | `Http::fake()`             | Mock voor tests            |
    | `Http::pool($callback)`    | Gelijktijdige requests     |
  </Accordion>

  <Accordion title="Overzicht van responscontrolemethoden">
    | Methode                 | Beschrijving |
    | ----------------------- | ------------ |
    | `successful()`          | 2xx          |
    | `failed()`              | 4xx of hoger |
    | `clientError()`         | 4xx          |
    | `serverError()`         | 5xx          |
    | `ok()`                  | 200          |
    | `created()`             | 201          |
    | `notFound()`            | 404          |
    | `unauthorized()`        | 401          |
    | `forbidden()`           | 403          |
    | `unprocessableEntity()` | 422          |
    | `tooManyRequests()`     | 429          |
  </Accordion>

  <Accordion title="Best practices voor integratie met externe API's">
    * Bundel de HTTP-clientlogica in een serviceklasse
    * Stel altijd time-outs in (`timeout()` en `connectTimeout()`)
    * Stel retries in voor tijdelijke storingen (`retry()`)
    * Gebruik in tests altijd `Http::fake()` en roep externe API's niet echt aan
    * Voeg `Http::preventStrayRequests()` toe aan je testsetup voor extra zekerheid
    * Beheer API-tokens en inloggegevens via omgevingsvariabelen en `config/services.php`
  </Accordion>
</AccordionGroup>


## Related topics

- [BlueskyManager en HasShortHand](/nl/packages/laravel-bluesky/bluesky-manager.md)
- [Praktische technieken voor Laravel Telescope](/nl/blog/telescope-introduction.md)
- [Upgraden van Laravel 8 naar 9](/nl/blog/upgrade-8-to-9.md)
- [Laravel Telescope](/nl/telescope.md)
- [Ontwikkelgids voor apps met de engine-API - VOICEVOX for Laravel](/nl/packages/laravel-voicevox/app-guide.md)
