Skip to content

Examples

Jean-Marc Strauven edited this page Dec 23, 2025 · 1 revision

Examples

Common use cases and practical examples for Laravel ApiRoute.


Basic API Setup

Simple Multi-Version API

// 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/users

Response:

{
    "users": [
        {"id": 1, "name": "John"},
        {"id": 2, "name": "Jane"}
    ]
}

Real-World E-Commerce API

Complete Setup

// 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']);

Controller Structure

app/Http/Controllers/Api/
├── V1/
│   ├── ProductController.php
│   ├── OrderController.php
│   └── CustomerController.php
├── V2/
│   ├── ProductController.php
│   ├── OrderController.php
│   └── CustomerController.php
└── V3/
    ├── ProductController.php
    └── QueryController.php

Version-Aware Controller

Checking Version in Controller

<?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(),
            ],
        ]);
    }
}

Authentication Per Version

Different Auth for Different Versions

// 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']);

Gradual Migration

Fallback for Missing Routes

// 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-endpoint

Response headers:

X-API-Version: v2
X-API-Version-Fallback: v1

Event Handling

Logging Deprecated Usage

// 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()
            );
        }
    }
}

Statistics Dashboard

API Analytics Controller

<?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;
    }
}

Client SDK Example

JavaScript Client

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');

Testing Examples

Feature Test

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');
});

Next Steps

Clone this wiki locally