Версионирование API в Laravel - это способ управлять изменениями в интерфейсе так, чтобы новые функции и обновления не ломали существующих интеграций. Для этого разные версии (например, v1, v2) можно вынести в отдельные пространства: в URL (/api/v1/resource), в заголовки (Accept, X-Api-Version), либо в параметры запроса (?version=2). Такой подход позволяет развивать API, сохраняя обратную совместимость, и делает Laravel удобным инструментом для создания масштабируемых и надёжных RESTful-сервисов.
Как выглядит базовое версионирование в Laravel
Старый путь:
/api/users
С версионированием:
/api/v1/users
Позже выпускаешь новую версию:
/api/v2/users
И обе продолжают работать одновременно.
Настройка v1 (первой версии)
1. Структура папок
app/
└── Http/
└── Controllers/
└── Api/
└── V1/
└── UserController.php
2. Маршруты
routes/api.php:
use Illuminate\Support\Facades\Route;
use App\Http\Controllers\Api\V1\UserController;
Route::prefix('v1')->group(function () {
Route::get('/users', [UserController::class, 'index']);
});
3. Контроллер V1
app/Http/Controllers/Api/V1/UserController.php:
namespace App\Http\Controllers\Api\V1;
use App\Http\Controllers\Controller;
use App\Models\User;
class UserController extends Controller
{
public function index()
{
return response()->json([
'version' => 'v1',
'data' => User::all(),
]);
}
}
Это базовая версия. Допустим, она возвращает всех пользователей.
Когда пора делать v2?
Типичные признаки:
изменилась структура данных
API должен возвращать другой формат
изменения ломают старых клиентов
логика устарела, её проще переписать
Например, в v2 хочется вернуть:
{
"id": 1,
"full_name": "John Doe",
"email": "john@doe.com"
}
А раньше было:
{
"id": 1,
"first_name": "John",
"last_name": "Doe",
"email": "john@doe.com"
}
Добавляем v2 (вторую версию API)
1. Новая структура
app/
└── Http/
└── Controllers/
└── Api/
├── V1/
└── V2/
└── UserController.php
2. Маршруты
routes/api.php:
use App\Http\Controllers\Api\V2\UserController as UserControllerV2;
Route::prefix('v2')->group(function () {
Route::get('/users', [UserControllerV2::class, 'index']);
});
3. Контроллер V2
namespace App\Http\Controllers\Api\V2;
use App\Http\Controllers\Controller;
use App\Models\User;
class UserController extends Controller
{
public function index()
{
$users = User::all()->map(function ($user) {
return [
'id' => $user->id,
'first_name' => $user->first_name,
'last_name' => $user->last_name,
'email' => $user->email,
];
});
return response()->json([
'version' => 'v2',
'data' => $users,
]);
}
}
Теперь у API два действующих формата: старый (v1) и новый (v2).
Другие способы версионирования
1. Версионирование через заголовки
Подходит тем, кто хочет "чистые" URLs:
/api/users
И передаёт версию через header:
X-Api-Version: 2
Пример middleware:
namespace App\Http\Middleware;
use Closure;
class ApiVersionMiddleware
{
public function handle($request, Closure $next)
{
$version = $request->header('X-Api-Version', 'v1');
app()->bind('api-version', fn() => $version);
return $next($request);
}
}
И далее в маршрутах:
if (app('api-version') === 'v2') {
require base_path('routes/api_v2.php');
} else {
require base_path('routes/api_v1.php');
}
Подход гибкий, но сложнее, чем /v1, /v2.
2. Версионирование через query-параметры
Ещё один вариант, когда URL остаётся универсальным:
/api/users?version=2
PHP-обработчик:
$version = request()->query('version', '1');
Middleware-версия:
namespace App\Http\Middleware;
use Closure;
class QueryApiVersionMiddleware
{
public function handle($request, Closure $next)
{
$version = $request->query('version', 'v1');
app()->bind('api-version', fn() => $version);
return $next($request);
}
}
Switch по версиям:
if (app('api-version') === 'v2') {
require base_path('routes/api_v2.php');
} else {
require base_path('routes/api_v1.php');
}
Когда удобно использовать query-versioning?
при A/B тестах API
при временной поддержке бета-функций
когда клиентам сложно менять URL, но легко менять параметры