Budowanie REST API w Laravel z Sanctum - kompletny przewodnik
Budowanie REST API
backendBudowanie REST API w Laravel z Sanctum - kompletny przewodnik
Laravel to jeden z najpopularniejszych frameworkow PHP, ktory oferuje eleganckie narzedzia do budowania nowoczesnych REST API. W polaczeniu z Laravel Sanctum - lekkim systemem uwierzytelniania - mozesz stworzyc bezpieczne, wydajne i skalowalne API w krotkim czasie. W tym przewodniku przeprowadzimy Cie przez caly proces: od konfiguracji projektu, przez projektowanie endpointow RESTful, az po uwierzytelnianie, walidacje, testowanie i dokumentacje.
Tworzenie projektu Laravel API#
Rozpocznij od utworzenia nowego projektu Laravel i skonfigurowania go pod API:
# Zainstaluj nowy projekt Laravel
composer create-project laravel/laravel my-api-project
cd my-api-project
# Skonfiguruj plik .env z polaczeniem do bazy danych
# DB_CONNECTION=mysql
# DB_HOST=127.0.0.1
# DB_PORT=3306
# DB_DATABASE=my_api
# DB_USERNAME=root
# DB_PASSWORD=secret
# Uruchom migracje
php artisan migrate
Laravel domyslnie zawiera plik routes/api.php, w ktorym definiujesz trasy API. Wszystkie trasy w tym pliku automatycznie otrzymuja prefiks /api i stosuja middleware api.
Struktura projektu API#
Dobrze zorganizowany projekt API w Laravel powinien miec przejrzysta strukture katalogow:
app/
Http/
Controllers/
Api/
V1/
ArticleController.php
AuthController.php
CommentController.php
Requests/
StoreArticleRequest.php
UpdateArticleRequest.php
Resources/
ArticleResource.php
ArticleCollection.php
Models/
Article.php
Comment.php
Policies/
ArticlePolicy.php
routes/
api.php
api_v1.php
tests/
Feature/
Api/
ArticleTest.php
AuthTest.php
Zasady projektowania RESTful API#
Zanim przejdziemy do implementacji, warto znac kluczowe zasady projektowania REST API:
Konwencje nazewnictwa zasobow#
Uzywaj rzeczownikow w liczbie mnogiej do nazywania zasobow i HTTP verbs do okreslania akcji:
| Metoda HTTP | Endpoint | Opis |
|-------------|----------------------|-------------------------|
| GET | /api/articles | Lista artykulow |
| GET | /api/articles/{id} | Pojedynczy artykul |
| POST | /api/articles | Utworz artykul |
| PUT/PATCH | /api/articles/{id} | Aktualizuj artykul |
| DELETE | /api/articles/{id} | Usun artykul |
Kody odpowiedzi HTTP#
Stosuj poprawne kody statusu HTTP:
- 200 OK - udane pobranie lub aktualizacja
- 201 Created - udane utworzenie zasobu
- 204 No Content - udane usuniecie
- 400 Bad Request - bledne dane wejsciowe
- 401 Unauthorized - brak uwierzytelniania
- 403 Forbidden - brak uprawnien
- 404 Not Found - zasob nie znaleziony
- 422 Unprocessable Entity - blad walidacji
- 429 Too Many Requests - przekroczenie limitu zapytan
- 500 Internal Server Error - blad serwera
API Resource Controllers#
Laravel pozwala na szybkie tworzenie kontrolerow z pelnym zestawem metod CRUD za pomoca jednej komendy:
php artisan make:controller Api/V1/ArticleController --api --model=Article
Flaga --api generuje kontroler bez metod create i edit (formularze HTML), ktore sa niepotrzebne w API:
<?php
namespace App\Http\Controllers\Api\V1;
use App\Http\Controllers\Controller;
use App\Http\Requests\StoreArticleRequest;
use App\Http\Requests\UpdateArticleRequest;
use App\Http\Resources\ArticleResource;
use App\Http\Resources\ArticleCollection;
use App\Models\Article;
use Illuminate\Http\JsonResponse;
use Illuminate\Http\Request;
class ArticleController extends Controller
{
/**
* Wyswietl liste artykulow z paginacja.
*/
public function index(Request $request): ArticleCollection
{
$articles = Article::query()
->with(['author', 'tags'])
->when($request->query('search'), function ($query, $search) {
$query->where('title', 'like', "%{$search}%")
->orWhere('content', 'like', "%{$search}%");
})
->when($request->query('tag'), function ($query, $tag) {
$query->whereHas('tags', fn ($q) => $q->where('slug', $tag));
})
->when($request->query('sort'), function ($query, $sort) {
$direction = str_starts_with($sort, '-') ? 'desc' : 'asc';
$column = ltrim($sort, '-');
$query->orderBy($column, $direction);
}, function ($query) {
$query->latest();
})
->paginate($request->query('per_page', 15));
return new ArticleCollection($articles);
}
/**
* Zapisz nowy artykul.
*/
public function store(StoreArticleRequest $request): JsonResponse
{
$article = Article::create([
...$request->validated(),
'user_id' => $request->user()->id,
'slug' => str()->slug($request->title),
]);
$article->tags()->sync($request->tag_ids ?? []);
return (new ArticleResource($article->load(['author', 'tags'])))
->response()
->setStatusCode(201);
}
/**
* Wyswietl pojedynczy artykul.
*/
public function show(Article $article): ArticleResource
{
return new ArticleResource(
$article->load(['author', 'tags', 'comments.user'])
);
}
/**
* Aktualizuj artykul.
*/
public function update(UpdateArticleRequest $request, Article $article): ArticleResource
{
$article->update($request->validated());
if ($request->has('tag_ids')) {
$article->tags()->sync($request->tag_ids);
}
return new ArticleResource($article->fresh(['author', 'tags']));
}
/**
* Usun artykul.
*/
public function destroy(Article $article): JsonResponse
{
$this->authorize('delete', $article);
$article->delete();
return response()->json(null, 204);
}
}
Rejestracja tras#
Zarejestruj kontroler w routes/api.php:
use App\Http\Controllers\Api\V1\ArticleController;
use App\Http\Controllers\Api\V1\AuthController;
use Illuminate\Support\Facades\Route;
// Trasy publiczne
Route::prefix('v1')->group(function () {
Route::post('/register', [AuthController::class, 'register']);
Route::post('/login', [AuthController::class, 'login']);
Route::get('/articles', [ArticleController::class, 'index']);
Route::get('/articles/{article}', [ArticleController::class, 'show']);
});
// Trasy chronione
Route::prefix('v1')->middleware('auth:sanctum')->group(function () {
Route::post('/logout', [AuthController::class, 'logout']);
Route::post('/articles', [ArticleController::class, 'store']);
Route::put('/articles/{article}', [ArticleController::class, 'update']);
Route::delete('/articles/{article}', [ArticleController::class, 'destroy']);
});
Eloquent API Resources - transformacja danych#
API Resources pozwalaja na kontrolowane transformowanie modeli Eloquent do odpowiedzi JSON. To warstwa abstrakcji miedzy modelami a odpowiedziami API:
php artisan make:resource ArticleResource
php artisan make:resource ArticleCollection --collection
Definiowanie zasobu#
<?php
namespace App\Http\Resources;
use Illuminate\Http\Request;
use Illuminate\Http\Resources\Json\JsonResource;
class ArticleResource extends JsonResource
{
/**
* Transformuj zasob do tablicy.
*/
public function toArray(Request $request): array
{
return [
'id' => $this->id,
'type' => 'article',
'attributes' => [
'title' => $this->title,
'slug' => $this->slug,
'excerpt' => str()->limit($this->content, 200),
'content' => $this->when(
$request->routeIs('articles.show'),
$this->content
),
'reading_time' => ceil(str_word_count($this->content) / 200) . ' min',
'published_at' => $this->published_at?->toISOString(),
'created_at' => $this->created_at->toISOString(),
'updated_at' => $this->updated_at->toISOString(),
],
'relationships' => [
'author' => [
'id' => $this->author->id,
'name' => $this->author->name,
],
'tags' => $this->whenLoaded('tags', function () {
return $this->tags->map(fn ($tag) => [
'id' => $tag->id,
'name' => $tag->name,
'slug' => $tag->slug,
]);
}),
'comments_count' => $this->whenCounted('comments'),
],
'links' => [
'self' => route('api.v1.articles.show', $this->id),
],
];
}
}
Kolekcja zasobow z metadanymi#
<?php
namespace App\Http\Resources;
use Illuminate\Http\Request;
use Illuminate\Http\Resources\Json\ResourceCollection;
class ArticleCollection extends ResourceCollection
{
public $collects = ArticleResource::class;
public function toArray(Request $request): array
{
return [
'data' => $this->collection,
'meta' => [
'total' => $this->total(),
'per_page' => $this->perPage(),
'current_page' => $this->currentPage(),
'last_page' => $this->lastPage(),
'has_more_pages' => $this->hasMorePages(),
],
'links' => [
'first' => $this->url(1),
'last' => $this->url($this->lastPage()),
'prev' => $this->previousPageUrl(),
'next' => $this->nextPageUrl(),
],
];
}
}
Laravel Sanctum - konfiguracja i uwierzytelnianie#
Laravel Sanctum oferuje dwa tryby uwierzytelniania: tokeny API dla aplikacji mobilnych i zewnetrznych klientow oraz uwierzytelnianie oparte na sesjach (cookies) dla SPA.
Instalacja i konfiguracja#
# Sanctum jest dolaczony domyslnie od Laravel 11
# Dla starszych wersji:
composer require laravel/sanctum
php artisan vendor:publish --provider="Laravel\Sanctum\SanctumServiceProvider"
php artisan migrate
Upewnij sie, ze model User uzywa traitu HasApiTokens:
<?php
namespace App\Models;
use Illuminate\Foundation\Auth\User as Authenticatable;
use Illuminate\Notifications\Notifiable;
use Laravel\Sanctum\HasApiTokens;
class User extends Authenticatable
{
use HasApiTokens, Notifiable;
protected $fillable = [
'name',
'email',
'password',
];
protected $hidden = [
'password',
'remember_token',
];
protected function casts(): array
{
return [
'email_verified_at' => 'datetime',
'password' => 'hashed',
];
}
}
Kontroler uwierzytelniania z tokenami#
<?php
namespace App\Http\Controllers\Api\V1;
use App\Http\Controllers\Controller;
use App\Models\User;
use Illuminate\Http\JsonResponse;
use Illuminate\Http\Request;
use Illuminate\Support\Facades\Hash;
use Illuminate\Validation\ValidationException;
class AuthController extends Controller
{
/**
* Rejestracja nowego uzytkownika.
*/
public function register(Request $request): JsonResponse
{
$validated = $request->validate([
'name' => ['required', 'string', 'max:255'],
'email' => ['required', 'string', 'email', 'max:255', 'unique:users'],
'password' => ['required', 'string', 'min:8', 'confirmed'],
]);
$user = User::create([
'name' => $validated['name'],
'email' => $validated['email'],
'password' => Hash::make($validated['password']),
]);
$token = $user->createToken('auth-token', ['*'], now()->addDays(30));
return response()->json([
'message' => 'Konto zostalo utworzone pomyslnie.',
'user' => [
'id' => $user->id,
'name' => $user->name,
'email' => $user->email,
],
'token' => $token->plainTextToken,
'token_type' => 'Bearer',
'expires_at' => $token->accessToken->expires_at->toISOString(),
], 201);
}
/**
* Logowanie uzytkownika i generowanie tokena.
*/
public function login(Request $request): JsonResponse
{
$request->validate([
'email' => ['required', 'email'],
'password' => ['required'],
]);
$user = User::where('email', $request->email)->first();
if (! $user || ! Hash::check($request->password, $user->password)) {
throw ValidationException::withMessages([
'email' => ['Podane dane logowania sa nieprawidlowe.'],
]);
}
// Opcjonalnie: usun stare tokeny
// $user->tokens()->delete();
$token = $user->createToken(
'auth-token',
$this->getAbilitiesForUser($user),
now()->addDays(30)
);
return response()->json([
'message' => 'Zalogowano pomyslnie.',
'user' => [
'id' => $user->id,
'name' => $user->name,
'email' => $user->email,
],
'token' => $token->plainTextToken,
'token_type' => 'Bearer',
]);
}
/**
* Wylogowanie - uniewaznij biezacy token.
*/
public function logout(Request $request): JsonResponse
{
$request->user()->currentAccessToken()->delete();
return response()->json([
'message' => 'Wylogowano pomyslnie.',
]);
}
/**
* Pobierz uprawnienia na podstawie roli uzytkownika.
*/
private function getAbilitiesForUser(User $user): array
{
return match ($user->role) {
'admin' => ['*'],
'editor' => ['articles:create', 'articles:update', 'articles:delete', 'comments:manage'],
'author' => ['articles:create', 'articles:update', 'comments:create'],
default => ['articles:read', 'comments:create'],
};
}
}
Sprawdzanie uprawnien tokenow#
// W kontrolerze lub middleware
public function store(StoreArticleRequest $request): JsonResponse
{
if (! $request->user()->tokenCan('articles:create')) {
abort(403, 'Brak uprawnien do tworzenia artykulow.');
}
// ... logika tworzenia
}
Uwierzytelnianie SPA z Sanctum#
Dla aplikacji Single Page Application (np. React, Vue, Angular) Sanctum oferuje uwierzytelnianie oparte na cookies:
Konfiguracja CORS i Sanctum#
W config/sanctum.php:
'stateful' => explode(',', env(
'SANCTUM_STATEFUL_DOMAINS',
'localhost,localhost:3000,127.0.0.1,127.0.0.1:8000'
)),
W config/cors.php:
return [
'paths' => ['api/*', 'sanctum/csrf-cookie'],
'allowed_origins' => [env('FRONTEND_URL', 'http://localhost:3000')],
'supports_credentials' => true,
];
Przeplyw uwierzytelniania SPA#
// 1. Pobierz CSRF cookie
await fetch('/sanctum/csrf-cookie', {
method: 'GET',
credentials: 'include',
});
// 2. Zaloguj sie
const response = await fetch('/api/v1/login', {
method: 'POST',
headers: {
'Content-Type': 'application/json',
'Accept': 'application/json',
'X-XSRF-TOKEN': getCookie('XSRF-TOKEN'),
},
credentials: 'include',
body: JSON.stringify({
email: 'user@example.com',
password: 'password',
}),
});
// 3. Kolejne zapytania sa automatycznie uwierzytelniane przez cookies
const articles = await fetch('/api/v1/articles', {
headers: { 'Accept': 'application/json' },
credentials: 'include',
});
Rate limiting - ograniczanie liczby zapytan#
Laravel oferuje elastyczny system rate limitingu zdefiniowany w AppServiceProvider lub RouteServiceProvider:
<?php
namespace App\Providers;
use Illuminate\Cache\RateLimiting\Limit;
use Illuminate\Http\Request;
use Illuminate\Support\Facades\RateLimiter;
use Illuminate\Support\ServiceProvider;
class AppServiceProvider extends ServiceProvider
{
public function boot(): void
{
// Globalny limit API
RateLimiter::for('api', function (Request $request) {
return Limit::perMinute(60)->by(
$request->user()?->id ?: $request->ip()
)->response(function () {
return response()->json([
'message' => 'Zbyt wiele zapytan. Sprobuj ponownie pozniej.',
'retry_after' => 60,
], 429);
});
});
// Restrykcyjny limit dla logowania
RateLimiter::for('auth', function (Request $request) {
return [
Limit::perMinute(5)->by($request->ip()),
Limit::perMinute(10)->by($request->input('email')),
];
});
// Wyzszy limit dla uwierzytelnionych uzytkownikow
RateLimiter::for('authenticated', function (Request $request) {
return $request->user()?->is_premium
? Limit::perMinute(120)->by($request->user()->id)
: Limit::perMinute(60)->by($request->user()->id);
});
}
}
Zastosowanie w trasach:
Route::middleware(['throttle:auth'])->group(function () {
Route::post('/login', [AuthController::class, 'login']);
Route::post('/register', [AuthController::class, 'register']);
});
Route::middleware(['auth:sanctum', 'throttle:authenticated'])->group(function () {
Route::apiResource('articles', ArticleController::class);
});
Wersjonowanie API#
Wersjonowanie pozwala na wprowadzanie zmian bez lamia kompatybilnosci z istniejacymi klientami:
Strategia z prefiksem URL#
// routes/api.php
Route::prefix('v1')->group(function () {
Route::apiResource('articles', Api\V1\ArticleController::class);
});
Route::prefix('v2')->group(function () {
Route::apiResource('articles', Api\V2\ArticleController::class);
});
Strategia z naglowkiem#
// app/Http/Middleware/ApiVersionMiddleware.php
namespace App\Http\Middleware;
use Closure;
use Illuminate\Http\Request;
class ApiVersionMiddleware
{
public function handle(Request $request, Closure $next)
{
$version = $request->header('X-API-Version', 'v1');
config(['app.api_version' => $version]);
return $next($request);
}
}
Wykorzystanie abstrakcyjnego kontrolera bazowego#
<?php
namespace App\Http\Controllers\Api;
use App\Http\Controllers\Controller;
abstract class BaseApiController extends Controller
{
protected function successResponse(mixed $data, int $statusCode = 200)
{
return response()->json([
'success' => true,
'data' => $data,
'api_version' => config('app.api_version', 'v1'),
], $statusCode);
}
protected function errorResponse(string $message, int $statusCode, array $errors = [])
{
return response()->json([
'success' => false,
'message' => $message,
'errors' => $errors ?: null,
'api_version' => config('app.api_version', 'v1'),
], $statusCode);
}
}
Walidacja z Form Requests#
Form Requests to elegancki sposob na wydzielenie logiki walidacji z kontrolerow:
php artisan make:request StoreArticleRequest
<?php
namespace App\Http\Requests;
use Illuminate\Foundation\Http\FormRequest;
use Illuminate\Contracts\Validation\Validator;
use Illuminate\Http\Exceptions\HttpResponseException;
class StoreArticleRequest extends FormRequest
{
public function authorize(): bool
{
return $this->user()->tokenCan('articles:create');
}
public function rules(): array
{
return [
'title' => ['required', 'string', 'max:255', 'unique:articles,title'],
'content' => ['required', 'string', 'min:100'],
'excerpt' => ['nullable', 'string', 'max:500'],
'category_id' => ['required', 'exists:categories,id'],
'tag_ids' => ['nullable', 'array'],
'tag_ids.*' => ['exists:tags,id'],
'published_at' => ['nullable', 'date', 'after_or_equal:today'],
'featured_image' => ['nullable', 'url', 'max:2048'],
'status' => ['sometimes', 'in:draft,published,scheduled'],
];
}
public function messages(): array
{
return [
'title.required' => 'Tytul artykulu jest wymagany.',
'title.unique' => 'Artykul o takim tytule juz istnieje.',
'content.min' => 'Tresc artykulu musi miec co najmniej :min znakow.',
'category_id.exists' => 'Wybrana kategoria nie istnieje.',
];
}
/**
* Zwroc odpowiedz JSON w przypadku bledu walidacji.
*/
protected function failedValidation(Validator $validator): void
{
throw new HttpResponseException(
response()->json([
'success' => false,
'message' => 'Blad walidacji danych.',
'errors' => $validator->errors(),
], 422)
);
}
}
Obsluga bledow i odpowiedzi API#
Skonfiguruj globalna obsluge bledow w bootstrap/app.php (Laravel 11+):
<?php
use Illuminate\Foundation\Application;
use Illuminate\Foundation\Configuration\Exceptions;
use Illuminate\Foundation\Configuration\Middleware;
use Illuminate\Auth\AuthenticationException;
use Illuminate\Database\Eloquent\ModelNotFoundException;
use Illuminate\Validation\ValidationException;
use Symfony\Component\HttpKernel\Exception\NotFoundHttpException;
use Symfony\Component\HttpKernel\Exception\ThrottleRequestsException;
return Application::configure(basePath: dirname(__DIR__))
->withRouting(
api: __DIR__.'/../routes/api.php',
health: '/up',
)
->withMiddleware(function (Middleware $middleware) {
$middleware->statefulApi();
})
->withExceptions(function (Exceptions $exceptions) {
$exceptions->shouldRenderJsonWhen(function ($request) {
return $request->is('api/*') || $request->expectsJson();
});
$exceptions->render(function (AuthenticationException $e) {
return response()->json([
'success' => false,
'message' => 'Wymagane uwierzytelnianie.',
], 401);
});
$exceptions->render(function (ModelNotFoundException $e) {
$model = class_basename($e->getModel());
return response()->json([
'success' => false,
'message' => "Nie znaleziono zasobu: {$model}.",
], 404);
});
$exceptions->render(function (NotFoundHttpException $e) {
return response()->json([
'success' => false,
'message' => 'Podany endpoint nie istnieje.',
], 404);
});
$exceptions->render(function (ThrottleRequestsException $e) {
return response()->json([
'success' => false,
'message' => 'Zbyt wiele zapytan. Sprobuj ponownie pozniej.',
'retry_after' => $e->getHeaders()['Retry-After'] ?? null,
], 429);
});
})
->create();
Testowanie API z PHPUnit#
Testy sa nieodzowna czescia profesjonalnego API. Laravel oferuje rozbudowane narzedzia do testow HTTP:
php artisan make:test Api/ArticleTest
<?php
namespace Tests\Feature\Api;
use App\Models\Article;
use App\Models\Category;
use App\Models\Tag;
use App\Models\User;
use Illuminate\Foundation\Testing\RefreshDatabase;
use Laravel\Sanctum\Sanctum;
use Tests\TestCase;
class ArticleTest extends TestCase
{
use RefreshDatabase;
private User $user;
private Category $category;
protected function setUp(): void
{
parent::setUp();
$this->user = User::factory()->create();
$this->category = Category::factory()->create();
}
/** @test */
public function guest_can_list_published_articles(): void
{
Article::factory()->count(5)->create(['status' => 'published']);
Article::factory()->count(2)->create(['status' => 'draft']);
$response = $this->getJson('/api/v1/articles');
$response->assertOk()
->assertJsonCount(5, 'data')
->assertJsonStructure([
'data' => [
'*' => [
'id',
'type',
'attributes' => ['title', 'slug', 'excerpt', 'created_at'],
'relationships' => ['author'],
'links' => ['self'],
],
],
'meta' => ['total', 'per_page', 'current_page'],
'links' => ['first', 'last', 'prev', 'next'],
]);
}
/** @test */
public function authenticated_user_can_create_article(): void
{
Sanctum::actingAs($this->user, ['articles:create']);
$tag = Tag::factory()->create();
$payload = [
'title' => 'Moj nowy artykul o Laravel',
'content' => str_repeat('Tresc artykulu testowego. ', 10),
'category_id' => $this->category->id,
'tag_ids' => [$tag->id],
'status' => 'published',
];
$response = $this->postJson('/api/v1/articles', $payload);
$response->assertCreated()
->assertJsonPath('data.attributes.title', 'Moj nowy artykul o Laravel')
->assertJsonPath('data.relationships.author.id', $this->user->id);
$this->assertDatabaseHas('articles', [
'title' => 'Moj nowy artykul o Laravel',
'user_id' => $this->user->id,
]);
}
/** @test */
public function unauthenticated_user_cannot_create_article(): void
{
$payload = [
'title' => 'Artykul bez autoryzacji',
'content' => str_repeat('Tresc artykulu. ', 10),
'category_id' => $this->category->id,
];
$response = $this->postJson('/api/v1/articles', $payload);
$response->assertUnauthorized();
}
/** @test */
public function it_validates_article_creation_data(): void
{
Sanctum::actingAs($this->user, ['articles:create']);
$response = $this->postJson('/api/v1/articles', [
'title' => '',
'content' => 'Krotka',
]);
$response->assertUnprocessable()
->assertJsonValidationErrors(['title', 'content', 'category_id']);
}
/** @test */
public function user_can_update_own_article(): void
{
Sanctum::actingAs($this->user, ['articles:update']);
$article = Article::factory()->create(['user_id' => $this->user->id]);
$response = $this->putJson("/api/v1/articles/{$article->id}", [
'title' => 'Zaktualizowany tytul',
]);
$response->assertOk()
->assertJsonPath('data.attributes.title', 'Zaktualizowany tytul');
}
/** @test */
public function user_cannot_delete_others_article(): void
{
$otherUser = User::factory()->create();
Sanctum::actingAs($this->user, ['articles:delete']);
$article = Article::factory()->create(['user_id' => $otherUser->id]);
$response = $this->deleteJson("/api/v1/articles/{$article->id}");
$response->assertForbidden();
}
/** @test */
public function it_paginates_articles(): void
{
Article::factory()->count(25)->create(['status' => 'published']);
$response = $this->getJson('/api/v1/articles?per_page=10&page=2');
$response->assertOk()
->assertJsonCount(10, 'data')
->assertJsonPath('meta.current_page', 2)
->assertJsonPath('meta.per_page', 10);
}
}
Uruchomienie testow:
# Wszystkie testy
php artisan test
# Tylko testy API
php artisan test --filter=ArticleTest
# Z pokryciem kodu
php artisan test --coverage --min=80
Dokumentacja API ze Scribe#
Automatyczna dokumentacja API znacznie ulatwia prace z API zarowno zespolowi deweloperow, jak i zewnetrznym konsumentom:
composer require knuckleswtf/scribe
php artisan vendor:publish --tag=scribe-config
Konfiguracja w config/scribe.php:
return [
'title' => 'My API Documentation',
'description' => 'Dokumentacja REST API',
'base_url' => env('APP_URL'),
'type' => 'external_static', // lub 'laravel'
'auth' => [
'enabled' => true,
'default' => true,
'in' => 'bearer',
'name' => 'Authorization',
'use_value' => 'Bearer {TOKEN}',
'placeholder' => '{TOKEN}',
],
];
Dodaj adnotacje do kontrolerow:
/**
* @group Artykuly
*
* Zarzadzanie artykulami w systemie.
*/
class ArticleController extends Controller
{
/**
* Lista artykulow
*
* Pobierz paginowana liste opublikowanych artykulow.
* Obsluguje filtrowanie, sortowanie i wyszukiwanie.
*
* @queryParam search string Wyszukaj artykuly po tytule lub tresci. Example: Laravel
* @queryParam tag string Filtruj po tagu. Example: php
* @queryParam sort string Sortuj wyniki. Prefiks '-' oznacza malejaco. Example: -created_at
* @queryParam per_page integer Liczba wynikow na strone (maks. 100). Example: 15
* @queryParam page integer Numer strony. Example: 1
*
* @response 200 scenario="Success" {
* "data": [
* {
* "id": 1,
* "type": "article",
* "attributes": {
* "title": "Wprowadzenie do Laravel",
* "slug": "wprowadzenie-do-laravel",
* "excerpt": "Laravel to nowoczesny framework PHP..."
* }
* }
* ],
* "meta": {"total": 50, "per_page": 15, "current_page": 1}
* }
*/
public function index(Request $request): ArticleCollection
{
// ...
}
}
Generowanie dokumentacji:
php artisan scribe:generate
Podsumowanie#
Budowanie REST API w Laravel z Sanctum to proces, ktory obejmuje wiele aspektow - od poprawnego projektowania endpointow, przez walidacje i uwierzytelnianie, az po testowanie i dokumentacje. Kluczowe elementy to:
- Spójne API Resources do kontrolowanej transformacji danych
- Laravel Sanctum do bezpiecznego uwierzytelniania tokenami i sesjami
- Form Requests do czystej i reuzytecznej walidacji
- Rate limiting do ochrony API przed naduzyciem
- Wersjonowanie do zapewnienia kompatybilnosci wstecznej
- Testy PHPUnit do weryfikacji poprawnosci dzialania
- Automatyczna dokumentacja do ulatwienia integracji
Stosujac te praktyki, stworzysz API, ktore jest bezpieczne, wydajne i latwe w utrzymaniu.
Potrzebujesz profesjonalnego API dla swojego projektu?#
W MDS Software Solutions Group specjalizujemy sie w projektowaniu i budowaniu skalowalnych REST API z wykorzystaniem Laravel, .NET i Node.js. Nasz zespol doswiadczonych deweloperow pomoze Ci stworzyc bezpieczne, wydajne i dobrze udokumentowane API, ktore spelni wymagania Twojego biznesu.
Skontaktuj sie z nami i porozmawiajmy o Twoim projekcie. Oferujemy bezplatna konsultacje, podczas ktorej ocenimy Twoje potrzeby i zaproponujemy optymalne rozwiazanie.
Zespół ekspertów programistycznych specjalizujących się w nowoczesnych technologiach webowych.