Preskočiť na obsah
API 26. máj 2026 · 9 min čítania

Ako navrhnúť REST API, ktoré nevyhorí o rok

Väčšina REST API vzniká rýchlo a funguje dobre — kým nepríde prvá breaking change, prvý klient, ktorý sa nevie prihlásiť, alebo prvý útok, ktorý zalapá server. Tento článok ukazuje, čo robiť od začiatku, aby API vydržalo roky bez bolesti.

DC

Dušan Chlpek

PHP vývojár, GEAR s.r.o. · 25+ rokov praxe

Prečo väčšina API „vyhorí"

API „vyhorí" keď pridáte druhého klienta a zistíte, že odpovede sú nekonzistentné. Alebo keď po roku potrebujete pridať pole do odpovede a neviete, ktorí klienti to prežijú. Alebo keď bot zalapá endpoint a aplikácia padne. Všetkým týmto problémom sa dá predísť rozhodnutiami, ktoré urobíte v prvý deň.

Verziovanie od prvého dňa

Verziovanie API je jediný spôsob, ako môžete neskôr urobiť breaking change bez toho, aby ste rozbili existujúcich klientov. V Laraveli mám dve stratégie:

URL verziovanie (odporúčané pre verejné API)

// routes/api.php
Route::prefix('v1')->group(base_path('routes/api_v1.php'));
Route::prefix('v2')->group(base_path('routes/api_v2.php'));

// Výsledok:
// GET /api/v1/users
// GET /api/v2/users

Header verziovanie (pre interné API)

// Klient posiela:
// Accept: application/vnd.myapp.v2+json

// Middleware číta verziu z Accept hlavičky
class ApiVersionMiddleware {
    public function handle($request, Closure $next) {
        $version = $this->parseVersion($request->header('Accept'));
        app()->instance('api.version', $version);
        return $next($request);
    }
}
Pozor: Nikdy neverziujte cez query parameter (?version=2). Nie je to cacheable a klienti to ľahko zabudnú posielať.

Autentifikácia: Sanctum vs. Passport

Toto je najčastejšia otázka pri Laravel API. Odpoveď závisí od use case:

Kritérium Laravel Sanctum Laravel Passport
SPA autentifikácia✅ Ideálne (cookie-based)Zbytočne komplexné
Mobilné aplikácie✅ API tokeny✅ OAuth2 tokeny
Third-party OAuth2❌ Nepodporuje✅ Plná podpora
Jednoduchosť setup-u✅ 15 minút2–4 hodiny
Token scopesJednoduché✅ Granulárne

Pravidlo: Pre 90 % projektov (SPA + mobilná aplikácia) použite Sanctum. Passport potrebujete len ak implementujete OAuth2 server — teda vydávate tokeny pre tretie strany.

// Inštalácia Sanctum
composer require laravel/sanctum
php artisan vendor:publish --provider="Laravel\Sanctum\SanctumServiceProvider"
php artisan migrate

// Vydanie tokenu
$token = $user->createToken('mobile-app', ['read:orders', 'write:orders'])->plainTextToken;

// Ochrana routy
Route::middleware('auth:sanctum')->get('/user', fn(Request $r) => $r->user());

Konzistentná štruktúra odpovedí

Každý endpoint by mal vracať rovnako štruktúrovanú odpoveď. Klienti (mobilné aplikácie, frontend) potom môžu napísať jeden parser a použiť ho všade. Používam tento vzor:

// Úspech
{
  "data": { "id": 1, "name": "Ján Novák" },
  "meta": { "version": "v1", "timestamp": "2026-05-26T10:00:00Z" }
}

// Zoznam
{
  "data": [{ "id": 1 }, { "id": 2 }],
  "meta": { "total": 100, "per_page": 15, "current_page": 1 }
}

// Chyba (RFC 7807 Problem Details)
{
  "type": "https://www.gear.sk/errors/validation",
  "title": "Chyba validácie",
  "status": 422,
  "detail": "Pole email je povinné.",
  "errors": { "email": ["Pole email je povinné."] }
}

API Resource v Laraveli

// app/Http/Resources/UserResource.php
class UserResource extends JsonResource {
    public function toArray(Request $request): array {
        return [
            'id'    => $this->id,
            'name'  => $this->name,
            'email' => $this->email,
            // Citlivé polia nikdy nevracajte (password_hash, remember_token)
        ];
    }
}

// V controlleri
return UserResource::collection(User::paginate(15));

Rate limiting — ochrana pred botmi a zneužitím

Laravel má vstavaný rate limiter, stačí ho nakonfigurovať v RouteServiceProvider:

// app/Providers/RouteServiceProvider.php
RateLimiter::for('api', function (Request $request) {
    return $request->user()
        ? Limit::perMinute(120)->by($request->user()->id)  // autentifikovaný
        : Limit::perMinute(20)->by($request->ip());         // anonymný
});

RateLimiter::for('login', function (Request $request) {
    return Limit::perMinute(5)->by($request->ip()); // brute force ochrana
});

// routes/api.php
Route::middleware(['throttle:api'])->group(function () {
    Route::post('/login', LoginController::class)->middleware('throttle:login');
    Route::middleware('auth:sanctum')->group(function () {
        // chránené endpointy
    });
});

Pri překročení limitu Laravel automaticky vráti HTTP 429 s hlavičkou Retry-After.

Error handling podľa RFC 7807

RFC 7807 (Problem Details for HTTP APIs) je štandard pre štruktúrované chybové odpovede. Laravel 11+ ho podporuje natívne cez respondWithProblem(), v starších verziách si vytvoríte vlastnú triedu:

// app/Exceptions/Handler.php
public function register(): void {
    $this->renderable(function (ValidationException $e, Request $request) {
        if ($request->expectsJson()) {
            return response()->json([
                'type'   => 'https://www.gear.sk/errors/validation',
                'title'  => 'Chyba validácie',
                'status' => 422,
                'detail' => 'Niektoré polia obsahujú neplatné hodnoty.',
                'errors' => $e->errors(),
            ], 422);
        }
    });

    $this->renderable(function (ModelNotFoundException $e, Request $request) {
        if ($request->expectsJson()) {
            return response()->json([
                'type'   => 'https://www.gear.sk/errors/not-found',
                'title'  => 'Záznam nenájdený',
                'status' => 404,
                'detail' => 'Požadovaný zdroj neexistuje.',
            ], 404);
        }
    });
}

Dokumentácia cez Scramble

Scramble je najlepší Laravel package pre automatickú OpenAPI dokumentáciu — číta vaše PHP kód (typy, PHPDoc, Resource triedy) a generuje interaktívnu dokumentáciu bez toho, aby ste pisali YAML ručne.

composer require dedoc/scramble

# Dokumentácia je dostupná na:
# http://localhost/docs/api
Tip: Scramble číta return typy PHP 8+ (: UserResource, : UserCollection) a automaticky generuje správnu schému. Napíšte správne typy a dokumentácia sa postará sama.

Testovanie — každý endpoint má test

API bez testov je API, ktoré sa bojíte zmeniť. V Laraveli s Pest-om je to rýchle:

// tests/Feature/Api/V1/UserTest.php
it('returns authenticated user', function () {
    $user = User::factory()->create();

    $response = $this->actingAs($user, 'sanctum')
        ->getJson('/api/v1/user');

    $response
        ->assertOk()
        ->assertJsonStructure(['data' => ['id', 'name', 'email']]);
});

it('rejects unauthenticated requests', function () {
    $this->getJson('/api/v1/user')->assertUnauthorized();
});

it('throttles login after 5 attempts', function () {
    foreach (range(1, 5) as $_) {
        $this->postJson('/api/v1/login', ['email' => 'x@x.sk', 'password' => 'wrong']);
    }
    $this->postJson('/api/v1/login', ['email' => 'x@x.sk', 'password' => 'wrong'])
        ->assertStatus(429);
});

Cache a ETag pre výkonné API

Pre endpointy, ktoré vracajú dáta, ktoré sa nemenia každú sekundu, pridajte caching. Laravel má vstavaný Cache facade a podpora ETag je jednoduchá:

// Controller s cache
public function index(Request $request): JsonResponse {
    $data = Cache::remember('products:all', 300, fn() =>
        ProductResource::collection(Product::active()->get())
    );

    $etag = md5(json_encode($data));
    if ($request->header('If-None-Match') === $etag) {
        return response()->json(null, 304);
    }

    return response()->json(['data' => $data])
        ->header('ETag', $etag)
        ->header('Cache-Control', 'public, max-age=300');
}

Záver: checklist pred spustením

REST API navrhnuté s týmito princípmi vydržia roky bez nutnosti rewrite-u. Ak riešite návrh alebo migráciu existujúceho API, rád pomôžem s auditom a odhadom nákladov.

Potrebujete robustné API pre váš projekt?

Navrhneme architektúru, implementujeme autentifikáciu, dokumentáciu a testy — a doručíme API, ktoré bude fungovať roky.

Ďalšie články

Zavolať E-mail Dopyt

Ochrana súkromia

Táto stránka využíva cookies pre nevyhnutné fungovanie. Rešpektujeme vaše súkromie a legislatívu GDPR.