Budowanie odpornych REST API w Laravelu

4 min. czytaniaZaktualizowano

Niezawodne API jest kontraktem na granicy aplikacji. Klient potrzebuje stabilnych nazw, przewidywalnej paginacji, użytecznych błędów i bezpiecznych ponowień. Laravel dostarcza elementy, ale jakość produkcyjna wynika ze świadomej polityki kompatybilności, autoryzacji i awarii.

Zacznij od polityki kompatybilności

Wersjonuj publiczne API, gdy niezależnie wdrażani klienci potrzebują jasnej obietnicy. /api/v1/products łatwo udokumentować i wycofać; nie jest rytuałem dla prywatnego backendu Inertia wdrażanego razem z serwerem. Po publikacji v1 nie zmieniaj po cichu znaczenia pola: wystaw v2 obok i określ okres migracji.

php
<?php

declare(strict_types=1);

use App\Http\Controllers\Api\V1\ProductController;
use Illuminate\Support\Facades\Route;

Route::prefix('v1')->middleware('auth:sanctum')->group(function (): void {
    Route::apiResource('products', ProductController::class);
});

apiResource daje przewidywalną powierzchnię. Route model binding odnajduje model, a policy rozstrzyga dostęp; nie duplikuj kontroli własności w kontrolerach.

Rozdziel wejście, przypadek użycia i reprezentację

Form Request waliduje wejście HTTP, akcja wykonuje przypadek użycia, a Resource posiada publiczną reprezentację. Dzięki temu przypadkowa kolumna modelu nie staje się częścią API na zawsze.

php
<?php

declare(strict_types=1);

namespace App\Http\Resources;

use Illuminate\Http\Request;
use Illuminate\Http\Resources\Json\JsonResource;

final class ProductResource extends JsonResource
{
    /** @return array<string, int|string> */
    public function toArray(Request $request): array
    {
        return ['id' => $this->resource->getKey(), 'name' => $this->resource->name, 'price_cents' => $this->resource->price_cents];
    }
}

Zwracaj ProductResource::collection($products), nie kolekcję Eloquent. Resource jest jednym miejscem na linki, zmienione nazwy pól i warunkowe relacje bez wycieku wnętrza aplikacji.

Nadaj tokenom wąskie abilities

Tokeny Sanctum są poświadczeniami, nie rolami. Wydaj tylko abilities potrzebne integracji i wymuś je middleware. Token raportowania nie powinien usuwać produktów.

php
<?php

declare(strict_types=1);

$token = $user->createToken('warehouse-sync', ['products:read']);

Route::get('/v1/products', ProductController::class)
    ->middleware(['auth:sanctum', 'abilities:products:read']);

Policy odpowiada za dostęp użytkownika, abilities za zakres konkretnego tokenu. Rotuj i unieważniaj tokeny, zapisuj ich cel i nigdy nie umieszczaj długowiecznego tokenu w JavaScripcie przeglądarki.

Dobierz paginację do zapytania

Offset (paginate()) daje total i numery stron, ale inserty przesuwają elementy między stronami, a duży offset jest kosztowny. Cursor (cursorPaginate()) szuka po stabilnym, zindeksowanym porządku, dlatego pasuje do feedów i dużych tabel dopisywanych w czasie.

php
<?php

declare(strict_types=1);

$products = Product::query()->orderBy('id')->cursorPaginate(perPage: 50);

return ProductResource::collection($products);

Kolejność kursora musi być deterministyczna. Użyj indeksowanego, unikalnego rozstrzygacza, np. created_at, id; nie paginuj kursorem po niestabilnej wartości obliczanej. Offset wybierz, gdy człowiek faktycznie potrzebuje strony 42.

Uczyń błędy czytelnymi dla maszyny

RFC 9457 Problem Details daje walidacji, autoryzacji i awariom jeden rozpoznawalny kształt. Zwracaj application/problem+json, stabilne type, krótkie title, HTTP status, instance albo ID korelacyjne oraz błędy pól tylko przy walidacji. Mapowanie centralizuj w konfiguracji wyjątków Laravela; kontrolery powinny zwracać zasoby sukcesu, nie różne tablice błędów.

json
{"type":"https://api.example.com/problems/validation","title":"The request is invalid.","status":422,"errors":{"name":["The name field is required."]}}

Nigdy nie wysyłaj klientowi wiadomości wyjątku, SQL ani stack trace.

Limituj chronioną możliwość

Nazwane limitery trzymaj w providerze, gdzie klucz i polityka są widoczne w review.

php
<?php

declare(strict_types=1);

use Illuminate\Cache\RateLimiting\Limit;
use Illuminate\Http\Request;
use Illuminate\Support\Facades\RateLimiter;

RateLimiter::for('partner-api', fn (Request $request): Limit => Limit::perMinute(120)->by((string) $request->user()?->getAuthIdentifier()));

Podłącz throttle:partner-api do grupy tras. Dla ruchu anonimowego użyj zaufanej tożsamości; niezaufany nagłówek forwarded-IP nią nie jest. Zwracaj 429 z informacją o ponowieniu i alarmuj o stałym limitowaniu partnera.

Pułapki i kiedy tego NIE używać

Nie dodawaj wersjonowanego REST tylko dlatego, że brzmi nowocześnie: wewnętrzny formularz nie potrzebuje publicznej obietnicy kompatybilności. Nie używaj kursora przy dowolnym sortowaniu bez indeksu i tie-breakera. Nie traktuj abilities Sanctum jako pełnego modelu uprawnień. HTTP retry nie jest exactly-once: płatności i webhooki potrzebują idempotency key oraz unikalnego ograniczenia bazy. Przed publikacją testuj feature każdy publiczny element kontraktu: sukces, oczekiwany błąd, autoryzację, paginację i ponowienie.

Powiązane artykuły

Wsparcie istniejącego systemu

Potrzebujesz pomocy z działającą aplikacją?

Pomagam firmom rozwijać działające systemy, porządkować wdrożenia i dodawać nowe funkcje bez dokładania chaosu do projektu.

Komentarze (0)
Zaloguj się, aby dodać komentarz

Musisz być zalogowany, aby dodać komentarz.

Zaloguj się

Potrzebujesz kogoś, kto weźmie odpowiedzialność za kolejny krok?

Porozmawiajmy o Twoim projekcie i określmy zakres, który ma sens dla Twoich celów.