-
-
Notifications
You must be signed in to change notification settings - Fork 4
Version Declaration
Learn how to declare and configure API versions.
New in v2.0: Define versions in config/apiroute.php. This approach ensures versions are properly registered on every application boot, solving the issue where versions were lost between tests.
Create a dedicated file for each API version:
routes/
├── api/
│ ├── v1.php
│ ├── v2.php
│ └── v3.php
// routes/api/v1.php
<?php
use App\Http\Controllers\Api\V1\UserController;
use Illuminate\Support\Facades\Route;
Route::apiResource('users', UserController::class);
Route::get('users/{user}/posts', [UserController::class, 'posts']);Add the versions section to config/apiroute.php:
'versions' => [
'v1' => [
'routes' => base_path('routes/api/v1.php'),
'middleware' => ['auth:sanctum'],
'status' => 'active',
],
'v2' => [
'routes' => base_path('routes/api/v2.php'),
'middleware' => ['auth:sanctum'],
'status' => 'beta',
],
],| Option | Type | Default | Description |
|---|---|---|---|
routes |
string |
required | Path to route file |
middleware |
array |
[] |
Additional middleware |
status |
string |
active |
active, beta, deprecated, sunset
|
deprecated_at |
string|null |
null |
Deprecation date (Y-m-d format) |
sunset_at |
string|null |
null |
Sunset date (Y-m-d format) |
successor |
string|null |
null |
Successor version name |
documentation |
string|null |
null |
Documentation URL |
rate_limit |
int|null |
null |
Requests per minute |
'versions' => [
'v1' => [
'routes' => base_path('routes/api/v1.php'),
'middleware' => ['auth:sanctum'],
'status' => 'deprecated',
'deprecated_at' => '2025-06-01',
'sunset_at' => '2025-12-01',
'successor' => 'v2',
'documentation' => 'https://docs.myapp.com/api/v1',
'rate_limit' => 100,
],
'v2' => [
'routes' => base_path('routes/api/v2.php'),
'middleware' => ['auth:sanctum'],
'status' => 'active',
'documentation' => 'https://docs.myapp.com/api/v2',
'rate_limit' => 1000,
],
'v3' => [
'routes' => base_path('routes/api/v3.php'),
'middleware' => ['auth:sanctum'],
'status' => 'beta',
],
],Note: This approach is still supported but not recommended for new projects. Versions registered this way may not persist between tests. See Migration Guide for upgrading.
Define versions in routes/api.php:
use Grazulex\ApiRoute\Facades\ApiRoute;
use Illuminate\Support\Facades\Route;
ApiRoute::version('v1', function () {
Route::get('users', [UserController::class, 'index']);
Route::post('users', [UserController::class, 'store']);
});The version() method returns a VersionDefinition object with a fluent API:
Set the version as the current stable version:
ApiRoute::version('v2', function () {
Route::apiResource('users', UserController::class);
})->current();Set the version as beta/preview:
ApiRoute::version('v3', function () {
Route::apiResource('users', UserController::class);
})->beta();Mark the version as deprecated with a date:
ApiRoute::version('v1', function () {
Route::apiResource('users', UserController::class);
})->deprecated('2025-06-01');You can also use Carbon:
use Carbon\Carbon;
ApiRoute::version('v1', function () {
// ...
})->deprecated(Carbon::parse('2025-06-01'));Set the end-of-life date for a version:
ApiRoute::version('v1', function () {
Route::apiResource('users', UserController::class);
})
->deprecated('2025-06-01')
->sunset('2025-12-01');Specify which version succeeds this one (for Link header):
ApiRoute::version('v1', function () {
// ...
})
->deprecated('2025-06-01')
->sunset('2025-12-01')
->setSuccessor('v2');Add a documentation URL for the version:
ApiRoute::version('v1', function () {
// ...
})->documentation('https://docs.example.com/api/v1');Apply version-specific rate limiting:
// Lower rate limit for deprecated version
ApiRoute::version('v1', function () {
// ...
})
->deprecated('2025-06-01')
->rateLimit(100); // 100 requests/minute
// Higher rate limit for current version
ApiRoute::version('v2', function () {
// ...
})
->current()
->rateLimit(1000); // 1000 requests/minuteAdd additional middleware to a version:
ApiRoute::version('v2', function () {
// ...
})->middleware(['auth:sanctum', 'throttle:api']);
// Or single middleware
ApiRoute::version('v2', function () {
// ...
})->middleware('auth:sanctum');Set a custom route name prefix:
ApiRoute::version('v2', function () {
Route::get('users', [UserController::class, 'index'])->name('users.index');
})->name_('api.v2.');
// Route name becomes: api.v2.users.indexuse Grazulex\ApiRoute\Facades\ApiRoute;
use Illuminate\Support\Facades\Route;
// Version 1 - Deprecated, will be sunset
ApiRoute::version('v1', function () {
Route::apiResource('users', App\Http\Controllers\Api\V1\UserController::class);
Route::apiResource('posts', App\Http\Controllers\Api\V1\PostController::class);
})
->deprecated('2025-06-01')
->sunset('2025-12-01')
->setSuccessor('v2')
->documentation('https://docs.myapp.com/api/v1')
->rateLimit(100);
// Version 2 - Current stable version
ApiRoute::version('v2', function () {
Route::apiResource('users', App\Http\Controllers\Api\V2\UserController::class);
Route::apiResource('posts', App\Http\Controllers\Api\V2\PostController::class);
Route::apiResource('comments', App\Http\Controllers\Api\V2\CommentController::class);
})
->current()
->documentation('https://docs.myapp.com/api/v2')
->rateLimit(1000);
// Version 3 - Beta preview
ApiRoute::version('v3', function () {
Route::apiResource('users', App\Http\Controllers\Api\V3\UserController::class);
})
->beta()
->documentation('https://docs.myapp.com/api/v3-beta')
->middleware(['auth:sanctum']);| Method | Description |
|---|---|
current() |
Mark as current stable version |
beta() |
Mark as beta/preview |
deprecated(string|Carbon $date) |
Set deprecation date |
sunset(string|Carbon $date) |
Set sunset date |
setSuccessor(string $version) |
Set successor version |
documentation(string $url) |
Set documentation URL |
rateLimit(int $requests) |
Set rate limit per minute |
middleware(array|string $middleware) |
Add middleware |
name_(string $name) |
Set route name prefix |
| Method | Return | Description |
|---|---|---|
name() |
string |
Get version name |
routes() |
Closure |
Get routes closure |
status() |
VersionStatus |
Get current status |
deprecationDate() |
?Carbon |
Get deprecation date |
sunsetDate() |
?Carbon |
Get sunset date |
successor() |
?string |
Get successor version |
documentationUrl() |
?string |
Get documentation URL |
rateLimit_() |
?int |
Get rate limit |
middlewares() |
array|string |
Get middleware |
routeName() |
?string |
Get route name prefix |
| Method | Return | Description |
|---|---|---|
isActive() |
bool |
Is status Active? |
isBeta() |
bool |
Is status Beta? |
isDeprecated() |
bool |
Is status Deprecated? |
isSunset() |
bool |
Is sunset (past sunset date)? |
isUsable() |
bool |
Can still be used? |
Access version information via the ApiRoute facade:
use Grazulex\ApiRoute\Facades\ApiRoute;
// Get all versions
$versions = ApiRoute::versions(); // Collection<VersionDefinition>
// Get specific version
$v1 = ApiRoute::getVersion('v1'); // ?VersionDefinition
// Get current version
$current = ApiRoute::currentVersion(); // ?VersionDefinition
// Check if version exists
if (ApiRoute::hasVersion('v2')) {
// ...
}
// Status checks
ApiRoute::isDeprecated('v1'); // bool
ApiRoute::isSunset('v1'); // bool
ApiRoute::isActive('v2'); // bool
// Resolve version from request
$version = ApiRoute::resolveVersion(request()); // stringuse Grazulex\ApiRoute\Support\VersionStatus;
enum VersionStatus: string
{
case Active = 'active';
case Beta = 'beta';
case Deprecated = 'deprecated';
case Sunset = 'sunset';
}
// Check if usable
$status->isUsable(); // true for Active, Beta, Deprecated
// Get label
$status->label(); // "Active", "Beta", etc.- Detection Strategies - How versions are detected
- Version Lifecycle - Managing version lifecycle
- HTTP Headers - Automatic response headers
Laravel ApiRoute - Complete API versioning lifecycle management for Laravel
Home | Getting Started | Examples | Configuration
Made with ❤️ for the Laravel community