Проектирование REST API на Laravel: принципы, версионирование и документирование в 2026 году
Введение: почему проектирование API важнее его реализации
Большинство разработчиков сначала пишут код, а потом задумываются об архитектуре. С REST API такой подход особенно разрушителен: неудачные решения на этапе проектирования превращаются в технический долг, который почти невозможно выплатить без breaking changes.
В 2026 году Laravel остаётся одним из самых популярных PHP-фреймворков для создания API-бэкендов. Его экосистема зрелая, инструментарий богатый — но это не отменяет необходимости думать перед тем, как писать. Эта статья — о том, как спроектировать REST API на Laravel правильно с первого раза.
Принципы RESTful-проектирования
Ресурсы и именование маршрутов
REST строится вокруг ресурсов, а не действий. URL должен обозначать существительное, а HTTP-метод — глагол.
- Правильно:
GET /api/v1/articles - Неправильно:
GET /api/v1/getArticles
Используйте множественное число для коллекций (/users), вложенные маршруты для отношений (/users/42/posts), и избегайте глубокой вложенности более двух уровней — это усложняет клиентский код.
HTTP-методы и их семантика
GET— получение ресурса или коллекции (безопасный, идемпотентный)POST— создание нового ресурсаPUT— полное обновление ресурса (идемпотентный)PATCH— частичное обновлениеDELETE— удаление ресурса (идемпотентный)
HTTP-статус-коды
Правильное использование статус-кодов — признак зрелого API. Не возвращайте 200 OK на каждый запрос с текстом ошибки в теле.
200— успешный GET/PUT/PATCH201— успешный POST (ресурс создан)204— успешный DELETE (тело пустое)400— ошибка валидации401— не аутентифицирован403— нет прав доступа404— ресурс не найден422— Unprocessable Entity (Laravel использует это для валидации)500— внутренняя ошибка сервера
Идемпотентность
Идемпотентный запрос при повторном выполнении даёт тот же результат. PUT /users/1 с теми же данными не должен создавать дубликаты. Это критично для надёжности при нестабильной сети.
Структура Laravel-проекта для API
Маршруты (Routes)
Все API-маршруты располагаются в routes/api.php. Laravel автоматически добавляет префикс /api и применяет middleware api.
// routes/api.php
use App\Http\Controllers\Api\V1\ArticleController;
Route::prefix('v1')->middleware('auth:sanctum')->group(function () {
Route::apiResource('articles', ArticleController::class);
Route::apiResource('users.posts', PostController::class)->shallow();
});Контроллеры (Controllers)
Используйте --api флаг при генерации контроллеров — он создаёт только нужные методы без create и edit.
php artisan make:controller Api/V1/ArticleController --api --model=ArticleПример контроллера с использованием API Resources:
<?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\Models\Article;
class ArticleController extends Controller
{
public function index()
{
$articles = Article::with('author')
->latest()
->paginate(15);
return ArticleResource::collection($articles);
}
public function store(StoreArticleRequest $request)
{
$article = Article::create($request->validated());
return new ArticleResource($article);
}
public function show(Article $article)
{
return new ArticleResource($article->load('author', 'tags'));
}
public function update(UpdateArticleRequest $request, Article $article)
{
$article->update($request->validated());
return new ArticleResource($article);
}
public function destroy(Article $article)
{
$article->delete();
return response()->noContent();
}
}API Resources
Никогда не возвращайте Eloquent-модели напрямую. API Resources — уровень трансформации данных, который защищает от случайной утечки полей и позволяет менять структуру ответа независимо от модели.
<?php
namespace App\Http\Resources;
use Illuminate\Http\Request;
use Illuminate\Http\Resources\Json\JsonResource;
class ArticleResource extends JsonResource
{
public function toArray(Request $request): array
{
return [
'id' => $this->id,
'title' => $this->title,
'slug' => $this->slug,
'content' => $this->content,
'published' => $this->published_at?->toIso8601String(),
'author' => new UserResource($this->whenLoaded('author')),
'tags' => TagResource::collection($this->whenLoaded('tags')),
'created_at' => $this->created_at->toIso8601String(),
];
}
}Form Requests
Выносите валидацию в Form Requests — это разгружает контроллеры и позволяет переиспользовать правила:
<?php
namespace App\Http\Requests;
use Illuminate\Foundation\Http\FormRequest;
class StoreArticleRequest extends FormRequest
{
public function authorize(): bool
{
return $this->user()->can('create', Article::class);
}
public function rules(): array
{
return [
'title' => ['required', 'string', 'max:255'],
'content' => ['required', 'string'],
'tags' => ['array'],
'tags.*' => ['integer', 'exists:tags,id'],
];
}
}Версионирование API: URI versioning vs Header versioning
Один из самых спорных вопросов в проектировании API. Существует два основных подхода:
URI Versioning
GET /api/v1/articles
GET /api/v2/articlesПлюсы: наглядно, легко тестировать в браузере, понятно для кэширования на уровне CDN. Минусы: URL технически не должен содержать информацию о версии протокола.
Header Versioning
GET /api/articles
Accept: application/vnd.myapp.v2+jsonПлюсы: «чище» с точки зрения REST-пуризма. Минусы: сложнее в отладке, плохо работает с браузерными инструментами.
Рекомендация 2026 года: используйте URI versioning для публичных и партнёрских API — это прагматичный выбор, который выбирает подавляющее большинство крупных компаний (GitHub, Stripe, Twilio). Header versioning оправдан только если вы управляете всеми клиентами и можете гарантировать корректную передачу заголовков.
В Laravel версионирование через URI организуется структурой папок:
app/Http/Controllers/Api/V1/ArticleController.php
app/Http/Controllers/Api/V2/ArticleController.php
routes/api/v1.php
routes/api/v2.phpАутентификация и авторизация: Laravel Sanctum vs Passport в 2026 году
Laravel Sanctum
Sanctum — рекомендуемое решение для большинства проектов в 2026 году. Поддерживает API-токены и SPA-аутентификацию через cookie-сессии. Прост в настройке, не требует OAuth-сервера.
// Установка
composer require laravel/sanctum
php artisan vendor:publish --provider="Laravel\Sanctum\SanctumServiceProvider"
php artisan migrate
// Выдача токена
$token = $user->createToken('mobile-app', ['articles:read', 'articles:write']);
return ['token' => $token->plainTextToken];
// Проверка способностей токена
if ($request->user()->tokenCan('articles:write')) {
// ...
}Laravel Passport
Passport реализует полноценный OAuth 2.0-сервер. Нужен когда: вы выдаёте токены сторонним приложениям, требуется Authorization Code Flow, или вы строите платформу с API для партнёров.
Итог: если у вас мобильное приложение или SPA — используйте Sanctum. Если строите OAuth-провайдер для сторонних клиентов — Passport.
Обработка ошибок и стандартизация ответов
Единый формат ответов критичен для клиентских разработчиков. Настройте глобальный обработчик исключений в app/Exceptions/Handler.php:
<?php
use Illuminate\Auth\AuthenticationException;
use Illuminate\Validation\ValidationException;
use Symfony\Component\HttpKernel\Exception\HttpException;
// В методе register():
$this->renderable(function (\Throwable $e, $request) {
if ($request->expectsJson()) {
if ($e instanceof ValidationException) {
return response()->json([
'message' => 'Validation failed',
'errors' => $e->errors(),
], 422);
}
if ($e instanceof AuthenticationException) {
return response()->json([
'message' => 'Unauthenticated.',
], 401);
}
if ($e instanceof HttpException) {
return response()->json([
'message' => $e->getMessage() ?: 'HTTP error',
], $e->getStatusCode());
}
return response()->json([
'message' => 'Server error',
], 500);
}
});Стандартный успешный ответ с пагинацией Laravel автоматически включает поля data, links и meta при использовании Resource::collection() с пагинированием — используйте это.
Документирование API с помощью Scribe или L5-Swagger
Scribe
Scribe — современный инструмент автогенерации документации для Laravel. Анализирует маршруты, Form Requests и docblock-комментарии, генерируя красивую HTML-документацию и OpenAPI-спецификацию.
composer require --dev knuckleswtf/scribe
php artisan vendor:publish --tag=scribe-config
php artisan scribe:generateАннотируйте контроллеры для лучшей документации:
/**
* @group Articles
*
* API для работы со статьями
*/
class ArticleController extends Controller
{
/**
* Список статей
*
* Возвращает пагинированный список всех опубликованных статей.
*
* @queryParam page integer Номер страницы. Example: 1
* @queryParam per_page integer Количество на странице (макс. 50). Example: 15
*/
public function index() { ... }
}L5-Swagger (darkaonline/l5-swagger)
Если команда предпочитает OpenAPI 3.0 и Swagger UI, L5-Swagger остаётся хорошим выбором. Используйте PHP-атрибуты вместо аннотаций в комментариях — это типобезопасно и поддерживается IDE.
composer require darkaonline/l5-swagger
php artisan vendor:publish --provider "L5Swagger\L5SwaggerServiceProvider"
php artisan l5-swagger:generateСовет: интегрируйте генерацию документации в CI/CD-пайплайн, чтобы документация всегда соответствовала коду.
Тестирование REST API в Laravel
Хорошо протестированный API — это API, которому доверяют. Laravel предоставляет мощные инструменты для Feature-тестов прямо из коробки.
<?php
namespace Tests\Feature\Api\V1;
use App\Models\Article;
use App\Models\User;
use Illuminate\Foundation\Testing\RefreshDatabase;
use Tests\TestCase;
class ArticleApiTest extends TestCase
{
use RefreshDatabase;
public function test_authenticated_user_can_create_article(): void
{
$user = User::factory()->create();
$response = $this->actingAs($user, 'sanctum')
->postJson('/api/v1/articles', [
'title' => 'Test Article',
'content' => 'Some content here',
]);
$response
->assertStatus(201)
->assertJsonStructure([
'data' => ['id', 'title', 'slug', 'content', 'created_at'],
])
->assertJsonPath('data.title', 'Test Article');
$this->assertDatabaseHas('articles', ['title' => 'Test Article']);
}
public function test_unauthenticated_request_returns_401(): void
{
$this->postJson('/api/v1/articles', ['title' => 'Test'])
->assertStatus(401);
}
public function test_validation_returns_422_with_errors(): void
{
$user = User::factory()->create();
$this->actingAs($user, 'sanctum')
->postJson('/api/v1/articles', [])
->assertStatus(422)
->assertJsonValidationErrors(['title', 'content']);
}
public function test_article_list_is_paginated(): void
{
$user = User::factory()->create();
Article::factory()->count(20)->create();
$this->actingAs($user, 'sanctum')
->getJson('/api/v1/articles')
->assertStatus(200)
->assertJsonStructure([
'data', 'links', 'meta' => ['total', 'per_page', 'current_page'],
]);
}
}Запускайте тесты с флагом --parallel для ускорения в CI:
php artisan test --parallel --coverageЗаключение: чек-лист готового API
Перед тем как считать API готовым к production, пройдитесь по этому чек-листу:
- Маршруты: используют существительные во множественном числе, версионированы через URI (
/api/v1/) - HTTP-методы: используются по семантике — GET, POST, PUT/PATCH, DELETE
- Статус-коды: 201 на создание, 204 на удаление, 422 на валидацию, 401/403 на auth-ошибки
- API Resources: все ответы проходят через Resource-трансформеры, модели не возвращаются напрямую
- Form Requests: валидация и авторизация вынесены из контроллеров
- Аутентификация: настроен Sanctum (или Passport для OAuth) с правильными scope/abilities
- Обработка ошибок: глобальный handler возвращает JSON-ответы для всех исключений
- Версионирование: маршруты и контроллеры структурированы по версиям
- Документация: сгенерирована через Scribe или L5-Swagger, интегрирована в CI
- Тесты: покрыты happy path, валидационные ошибки и auth-сценарии
- Rate Limiting: настроен через
RateLimiter::for()вRouteServiceProvider - N+1 проблемы: устранены через eager loading, подключён Laravel Debugbar или Telescope для мониторинга
Качественное REST API на Laravel — это не только правильный код, но и продуманная архитектура, предсказуемое поведение и хорошая документация. Инвестируйте время в проектирование — это окупится многократно при масштабировании и командной разработке.
Технологии
Теги
Руслан Исмаилов
Senior Web / Backend разработчик. Senior web/backend разработчик с 9-летним опытом. Стек: PHP, Laravel, PostgreSQL, Redis, Docker, Kubernetes, REST, микросервисы, CI/CD. Подробнее обо мне →