-
-
Notifications
You must be signed in to change notification settings - Fork 4
Examples
Jean-Marc Strauven edited this page Dec 23, 2025
·
1 revision
Common use cases and practical examples for Laravel ApiRoute.
// routes/api.php
use Grazulex\ApiRoute\Facades\ApiRoute;
use Illuminate\Support\Facades\Route;
// Version 1
ApiRoute::version('v1', function () {
Route::get('users', function () {
return response()->json([
'users' => [
['id' => 1, 'name' => 'John'],
['id' => 2, 'name' => 'Jane'],
]
]);
});
})->current();Request:
curl http://localhost/api/v1/usersResponse:
{
"users": [
{"id": 1, "name": "John"},
{"id": 2, "name": "Jane"}
]
}// routes/api.php
use Grazulex\ApiRoute\Facades\ApiRoute;
use Illuminate\Support\Facades\Route;
// Version 1 - Legacy (deprecated)
ApiRoute::version('v1', function () {
Route::apiResource('products', App\Http\Controllers\Api\V1\ProductController::class);
Route::apiResource('orders', App\Http\Controllers\Api\V1\OrderController::class);
Route::apiResource('customers', App\Http\Controllers\Api\V1\CustomerController::class);
})
->deprecated('2025-06-01')
->sunset('2025-12-01')
->setSuccessor('v2')
->documentation('https://api.myshop.com/docs/v1')
->rateLimit(100);
// Version 2 - Current
ApiRoute::version('v2', function () {
Route::apiResource('products', App\Http\Controllers\Api\V2\ProductController::class);
Route::apiResource('orders', App\Http\Controllers\Api\V2\OrderController::class);
Route::apiResource('customers', App\Http\Controllers\Api\V2\CustomerController::class);
// New endpoints in v2
Route::get('products/{product}/reviews', [App\Http\Controllers\Api\V2\ProductController::class, 'reviews']);
Route::post('orders/{order}/refund', [App\Http\Controllers\Api\V2\OrderController::class, 'refund']);
})
->current()
->documentation('https://api.myshop.com/docs/v2')
->rateLimit(1000);
// Version 3 - Beta
ApiRoute::version('v3', function () {
Route::apiResource('products', App\Http\Controllers\Api\V3\ProductController::class);
// New GraphQL-like endpoint
Route::post('query', App\Http\Controllers\Api\V3\QueryController::class);
})
->beta()
->documentation('https://api.myshop.com/docs/v3-beta')
->middleware(['auth:sanctum']);app/Http/Controllers/Api/
├── V1/
│ ├── ProductController.php
│ ├── OrderController.php
│ └── CustomerController.php
├── V2/
│ ├── ProductController.php
│ ├── OrderController.php
│ └── CustomerController.php
└── V3/
├── ProductController.php
└── QueryController.php
<?php
namespace App\Http\Controllers\Api\V2;
use App\Http\Controllers\Controller;
use App\Models\Product;
use Illuminate\Http\Request;
class ProductController extends Controller
{
public function index(Request $request)
{
$products = Product::query();
// Version-specific behavior
if ($request->isDeprecatedVersion()) {
// Log for migration tracking
logger()->warning('Deprecated API version used', [
'version' => $request->apiVersion(),
'endpoint' => $request->path(),
'user' => $request->user()?->id,
]);
}
// Add v2-specific includes
$products->with(['category', 'brand', 'reviews']);
return response()->json([
'data' => $products->paginate(20),
'meta' => [
'api_version' => $request->apiVersion(),
],
]);
}
}// Version 1 - Basic API key auth
ApiRoute::version('v1', function () {
Route::apiResource('users', App\Http\Controllers\Api\V1\UserController::class);
})
->middleware(['auth.apikey']);
// Version 2 - Sanctum tokens
ApiRoute::version('v2', function () {
Route::apiResource('users', App\Http\Controllers\Api\V2\UserController::class);
})
->middleware(['auth:sanctum']);
// Version 3 - OAuth 2.0
ApiRoute::version('v3', function () {
Route::apiResource('users', App\Http\Controllers\Api\V3\UserController::class);
})
->middleware(['auth:passport']);// config/apiroute.php
'fallback' => [
'enabled' => true,
'strategy' => 'previous',
'add_header' => true,
],// Version 1 has all legacy endpoints
ApiRoute::version('v1', function () {
Route::get('legacy-endpoint', [LegacyController::class, 'handle']);
Route::apiResource('users', App\Http\Controllers\Api\V1\UserController::class);
Route::apiResource('posts', App\Http\Controllers\Api\V1\PostController::class);
});
// Version 2 only has updated endpoints
// Missing routes fall back to v1
ApiRoute::version('v2', function () {
Route::apiResource('users', App\Http\Controllers\Api\V2\UserController::class);
// 'posts' and 'legacy-endpoint' will fallback to v1
})->current();Request to v2 for legacy endpoint:
curl http://localhost/api/v2/legacy-endpointResponse headers:
X-API-Version: v2
X-API-Version-Fallback: v1// app/Listeners/LogDeprecatedUsage.php
<?php
namespace App\Listeners;
use Grazulex\ApiRoute\Events\DeprecatedVersionAccessed;
use Illuminate\Support\Facades\Log;
use Illuminate\Support\Facades\Cache;
class LogDeprecatedUsage
{
public function handle(DeprecatedVersionAccessed $event): void
{
// Log the access
Log::channel('api')->warning('Deprecated API access', [
'version' => $event->version->name(),
'sunset_date' => $event->version->sunsetDate()?->format('Y-m-d'),
'endpoint' => $event->request->path(),
'method' => $event->request->method(),
'user_id' => $event->request->user()?->id,
'ip' => $event->request->ip(),
]);
// Track unique users on deprecated version
$key = "deprecated_users:{$event->version->name()}";
if ($event->request->user()) {
Cache::tags(['api-metrics'])->add(
"{$key}:{$event->request->user()->id}",
true,
now()->addDay()
);
}
}
}<?php
namespace App\Http\Controllers\Admin;
use App\Http\Controllers\Controller;
use Grazulex\ApiRoute\Contracts\VersionTrackerInterface;
use Grazulex\ApiRoute\Facades\ApiRoute;
class ApiAnalyticsController extends Controller
{
public function __construct(
private VersionTrackerInterface $tracker
) {}
public function index()
{
$versions = ApiRoute::versions();
$stats = $this->tracker->getAllStats(30);
$totalRequests = array_sum(array_column($stats, 'total_requests'));
$data = $versions->map(function ($version) use ($stats, $totalRequests) {
$versionStats = $stats[$version->name()] ?? [];
return [
'name' => $version->name(),
'status' => $version->status()->value,
'deprecated_at' => $version->deprecationDate()?->format('Y-m-d'),
'sunset_at' => $version->sunsetDate()?->format('Y-m-d'),
'requests' => $versionStats['total_requests'] ?? 0,
'percentage' => $totalRequests > 0
? round(($versionStats['total_requests'] ?? 0) / $totalRequests * 100, 1)
: 0,
'success_rate' => $this->calculateSuccessRate($versionStats),
];
});
return view('admin.api-analytics', compact('data', 'totalRequests'));
}
private function calculateSuccessRate(array $stats): float
{
$total = $stats['total_requests'] ?? 0;
$success = $stats['success_requests'] ?? 0;
return $total > 0 ? round($success / $total * 100, 1) : 0;
}
}class ApiClient {
constructor(options = {}) {
this.baseUrl = options.baseUrl || 'https://api.example.com';
this.version = options.version || 'v2';
this.strategy = options.strategy || 'uri';
}
async request(method, endpoint, data = null) {
const url = this.buildUrl(endpoint);
const headers = this.buildHeaders();
const response = await fetch(url, {
method,
headers,
body: data ? JSON.stringify(data) : null,
});
// Check for deprecation warning
this.checkDeprecation(response);
return response.json();
}
buildUrl(endpoint) {
if (this.strategy === 'uri') {
return `${this.baseUrl}/api/${this.version}/${endpoint}`;
}
return `${this.baseUrl}/api/${endpoint}`;
}
buildHeaders() {
const headers = {
'Content-Type': 'application/json',
'Accept': 'application/json',
};
if (this.strategy === 'header') {
headers['X-API-Version'] = this.version;
}
return headers;
}
checkDeprecation(response) {
const deprecation = response.headers.get('Deprecation');
const sunset = response.headers.get('Sunset');
if (deprecation) {
console.warn(
`API version ${this.version} is deprecated. ` +
`Sunset date: ${sunset || 'TBA'}. ` +
`Please upgrade to a newer version.`
);
}
}
// Convenience methods
get(endpoint) {
return this.request('GET', endpoint);
}
post(endpoint, data) {
return this.request('POST', endpoint, data);
}
put(endpoint, data) {
return this.request('PUT', endpoint, data);
}
delete(endpoint) {
return this.request('DELETE', endpoint);
}
}
// Usage
const api = new ApiClient({ version: 'v2' });
const users = await api.get('users');use Grazulex\ApiRoute\Facades\ApiRoute;
test('v1 returns deprecation headers', function () {
ApiRoute::version('v1', fn() => Route::get('test', fn() => 'ok'))
->deprecated('2025-06-01')
->sunset('2025-12-01');
$response = $this->get('/api/v1/test');
$response->assertOk();
$response->assertHeader('X-API-Version', 'v1');
$response->assertHeader('X-API-Version-Status', 'deprecated');
$response->assertHeader('Deprecation');
$response->assertHeader('Sunset');
});
test('sunset version returns 410', function () {
ApiRoute::version('v1', fn() => Route::get('test', fn() => 'ok'))
->sunset(now()->subDay());
$response = $this->get('/api/v1/test');
$response->assertStatus(410);
$response->assertJson(['error' => 'api_version_sunset']);
});
test('fallback works when route missing', function () {
ApiRoute::version('v1', fn() => Route::get('legacy', fn() => 'v1'));
ApiRoute::version('v2', fn() => Route::get('new', fn() => 'v2'))->current();
$response = $this->get('/api/v2/legacy');
$response->assertOk();
$response->assertContent('v1');
$response->assertHeader('X-API-Version-Fallback', 'v1');
});- Getting Started - Quick start guide
- Configuration - Full configuration reference
- Advanced Usage - Advanced patterns
Laravel ApiRoute - Complete API versioning lifecycle management for Laravel
Home | Getting Started | Examples | Configuration
Made with ❤️ for the Laravel community