Navigation

Laravel

Implementing API Versioning in Laravel: Multiple Strategies

Master comprehensive API versioning in Laravel with URL-based, header-based, and query parameter strategies. Learn backward compatibility, version-specific transformers, and testing approaches.

Master comprehensive API versioning in Laravel using URL-based, header-based, and query parameter strategies while maintaining backward compatibility and clean architecture.

Table Of Contents

Understanding API Versioning Strategies

API versioning is crucial for maintaining backward compatibility while allowing your API to evolve. Laravel provides flexible approaches to implement versioning, from simple URL-based versioning to sophisticated content negotiation. The key is choosing the right strategy for your specific use case and implementing it consistently.

Effective API versioning becomes essential when building scalable Laravel APIs that need to serve multiple client applications with different feature requirements. The strategy you choose impacts developer experience, cache efficiency, and maintenance overhead.

URL-Based Versioning Implementation

Route-Based Version Management

Implement clean URL-based versioning with proper route organization:

<?php

// routes/api.php
use Illuminate\Support\Facades\Route;
use App\Http\Controllers\Api;

// Version 1 Routes
Route::prefix('v1')->group(function () {
    Route::apiResource('users', Api\V1\UserController::class);
    Route::apiResource('posts', Api\V1\PostController::class);
    Route::apiResource('comments', Api\V1\CommentController::class);
    
    Route::get('dashboard', [Api\V1\DashboardController::class, 'index']);
    Route::post('auth/login', [Api\V1\AuthController::class, 'login']);
    Route::post('auth/logout', [Api\V1\AuthController::class, 'logout']);
    Route::post('auth/refresh', [Api\V1\AuthController::class, 'refresh']);
});

// Version 2 Routes
Route::prefix('v2')->group(function () {
    Route::apiResource('users', Api\V2\UserController::class);
    Route::apiResource('posts', Api\V2\PostController::class);
    Route::apiResource('comments', Api\V2\CommentController::class);
    
    // New endpoints in v2
    Route::apiResource('categories', Api\V2\CategoryController::class);
    Route::get('analytics', [Api\V2\AnalyticsController::class, 'index']);
    
    Route::get('dashboard', [Api\V2\DashboardController::class, 'index']);
    Route::post('auth/login', [Api\V2\AuthController::class, 'login']);
    Route::post('auth/logout', [Api\V2\AuthController::class, 'logout']);
    Route::post('auth/refresh', [Api\V2\AuthController::class, 'refresh']);
});

// Default to latest version
Route::redirect('/api/users', '/api/v2/users');
Route::redirect('/api/posts', '/api/v2/posts');

// Version-aware route helper
if (!function_exists('versioned_api_route')) {
    function versioned_api_route(string $name, array $parameters = [], string $version = null): string
    {
        $version = $version ?? config('api.default_version', 'v2');
        $routeName = "api.{$version}.{$name}";
        
        if (!Route::has($routeName)) {
            // Fallback to v1 if route doesn't exist in specified version
            $routeName = "api.v1.{$name}";
        }
        
        return route($routeName, $parameters);
    }
}

Version-Aware Base Controller

Create a base controller that handles version-specific logic:

<?php

namespace App\Http\Controllers\Api;

use Illuminate\Http\Request;
use Illuminate\Http\JsonResponse;
use App\Http\Controllers\Controller;
use App\Services\ApiVersionService;

abstract class BaseApiController extends Controller
{
    protected string $version;
    protected ApiVersionService $versionService;
    
    public function __construct(ApiVersionService $versionService)
    {
        $this->versionService = $versionService;
        $this->version = $this->determineVersion();
        
        // Apply version-specific middleware
        $this->middleware($this->getVersionMiddleware());
    }
    
    protected function determineVersion(): string
    {
        // Get version from URL segment
        $segment = request()->segment(2);
        
        if (preg_match('/^v\d+$/', $segment)) {
            return $segment;
        }
        
        return config('api.default_version', 'v1');
    }
    
    protected function getVersionMiddleware(): array
    {
        $middleware = ['api'];
        
        // Add version-specific middleware
        switch ($this->version) {
            case 'v1':
                $middleware[] = 'api.v1.deprecated';
                break;
                
            case 'v2':
                $middleware[] = 'api.v2.features';
                break;
        }
        
        return $middleware;
    }
    
    protected function respond($data, int $status = 200, array $headers = []): JsonResponse
    {
        $response = [
            'data' => $data,
            'meta' => [
                'version' => $this->version,
                'timestamp' => now()->toISOString(),
            ],
        ];
        
        // Add version-specific response modifications
        $response = $this->versionService->transformResponse($response, $this->version);
        
        return response()->json($response, $status, $headers);
    }
    
    protected function respondWithError(string $message, int $status = 400, array $errors = []): JsonResponse
    {
        $response = [
            'error' => [
                'message' => $message,
                'status' => $status,
            ],
            'meta' => [
                'version' => $this->version,
                'timestamp' => now()->toISOString(),
            ],
        ];
        
        if (!empty($errors)) {
            $response['error']['details'] = $errors;
        }
        
        return response()->json($response, $status);
    }
    
    protected function respondWithPagination($paginator, $transformer = null): JsonResponse
    {
        $data = $transformer ? $transformer->transformCollection($paginator->items()) : $paginator->items();
        
        $response = [
            'data' => $data,
            'pagination' => [
                'current_page' => $paginator->currentPage(),
                'per_page' => $paginator->perPage(),
                'total' => $paginator->total(),
                'last_page' => $paginator->lastPage(),
                'has_more' => $paginator->hasMorePages(),
            ],
            'meta' => [
                'version' => $this->version,
                'timestamp' => now()->toISOString(),
            ],
        ];
        
        // Version-specific pagination adjustments
        if ($this->version === 'v1') {
            // V1 uses different pagination format
            $response['pagination'] = [
                'page' => $paginator->currentPage(),
                'limit' => $paginator->perPage(),
                'total_items' => $paginator->total(),
                'total_pages' => $paginator->lastPage(),
            ];
        }
        
        return response()->json($response);
    }
}

// Version-specific Controllers
namespace App\Http\Controllers\Api\V1;

use App\Http\Controllers\Api\BaseApiController;
use App\Models\User;
use App\Http\Requests\Api\V1\CreateUserRequest;
use App\Http\Requests\Api\V1\UpdateUserRequest;
use App\Transformers\V1\UserTransformer;

class UserController extends BaseApiController
{
    protected UserTransformer $transformer;
    
    public function __construct(
        \App\Services\ApiVersionService $versionService,
        UserTransformer $transformer
    ) {
        parent::__construct($versionService);
        $this->transformer = $transformer;
    }
    
    public function index()
    {
        $users = User::paginate(15);
        
        return $this->respondWithPagination(
            $users,
            $this->transformer
        );
    }
    
    public function show(User $user)
    {
        return $this->respond(
            $this->transformer->transform($user)
        );
    }
    
    public function store(CreateUserRequest $request)
    {
        $user = User::create($request->validated());
        
        return $this->respond(
            $this->transformer->transform($user),
            201
        );
    }
    
    public function update(UpdateUserRequest $request, User $user)
    {
        $user->update($request->validated());
        
        return $this->respond(
            $this->transformer->transform($user)
        );
    }
    
    public function destroy(User $user)
    {
        $user->delete();
        
        return response()->json(null, 204);
    }
}

namespace App\Http\Controllers\Api\V2;

use App\Http\Controllers\Api\BaseApiController;
use App\Models\User;
use App\Http\Requests\Api\V2\CreateUserRequest;
use App\Http\Requests\Api\V2\UpdateUserRequest;
use App\Transformers\V2\UserTransformer;

class UserController extends BaseApiController
{
    protected UserTransformer $transformer;
    
    public function __construct(
        \App\Services\ApiVersionService $versionService,
        UserTransformer $transformer
    ) {
        parent::__construct($versionService);
        $this->transformer = $transformer;
    }
    
    public function index()
    {
        // V2 supports more advanced filtering
        $users = User::when(request('role'), function ($query, $role) {
                     return $query->whereHas('roles', function ($q) use ($role) {
                         $q->where('name', $role);
                     });
                 })
                 ->when(request('active'), function ($query, $active) {
                     return $query->where('is_active', $active === 'true');
                 })
                 ->when(request('created_after'), function ($query, $date) {
                     return $query->where('created_at', '>=', $date);
                 })
                 ->paginate(request('per_page', 20));
        
        return $this->respondWithPagination(
            $users,
            $this->transformer
        );
    }
    
    public function show(User $user)
    {
        // V2 includes additional relationships
        $user->load(['profile', 'roles', 'preferences']);
        
        return $this->respond(
            $this->transformer->transform($user)
        );
    }
    
    public function store(CreateUserRequest $request)
    {
        $user = User::create($request->validated());
        
        // V2 automatically creates user profile
        $user->profile()->create($request->input('profile', []));
        
        return $this->respond(
            $this->transformer->transform($user->load('profile')),
            201
        );
    }
    
    public function update(UpdateUserRequest $request, User $user)
    {
        $user->update($request->validated());
        
        // V2 supports profile updates in same request
        if ($request->has('profile')) {
            $user->profile()->updateOrCreate(
                ['user_id' => $user->id],
                $request->input('profile')
            );
        }
        
        return $this->respond(
            $this->transformer->transform($user->load('profile'))
        );
    }
    
    public function destroy(User $user)
    {
        // V2 supports soft deletes
        $user->delete();
        
        return $this->respond([
            'message' => 'User soft deleted successfully',
            'deleted_at' => $user->deleted_at,
        ]);
    }
}

Header-Based Versioning

Accept Header Versioning

Implement versioning through HTTP headers:

<?php

namespace App\Http\Middleware;

use Closure;
use Illuminate\Http\Request;
use Symfony\Component\HttpFoundation\Response;

class ApiVersionMiddleware
{
    protected array $supportedVersions = ['v1', 'v2'];
    protected string $defaultVersion = 'v2';
    
    public function handle(Request $request, Closure $next): Response
    {
        $version = $this->determineVersion($request);
        
        // Set version in request for use by controllers
        $request->attributes->set('api_version', $version);
        
        // Add version to response headers
        $response = $next($request);
        
        if ($response instanceof \Illuminate\Http\JsonResponse) {
            $response->header('API-Version', $version);
            $response->header('Supported-Versions', implode(', ', $this->supportedVersions));
            
            // Add deprecation warnings for old versions
            if ($version === 'v1') {
                $response->header('Deprecation', 'true');
                $response->header('Sunset', '2024-12-31');
                $response->header('Link', '</api/v2>; rel="successor-version"');
            }
        }
        
        return $response;
    }
    
    protected function determineVersion(Request $request): string
    {
        // 1. Check Accept header with vendor-specific media type
        $accept = $request->header('Accept');
        if ($accept && preg_match('/application\/vnd\.myapi\.(v\d+)\+json/', $accept, $matches)) {
            $version = $matches[1];
            if (in_array($version, $this->supportedVersions)) {
                return $version;
            }
        }
        
        // 2. Check custom API-Version header
        $headerVersion = $request->header('API-Version');
        if ($headerVersion && in_array($headerVersion, $this->supportedVersions)) {
            return $headerVersion;
        }
        
        // 3. Check URL path (fallback)
        $pathSegments = explode('/', trim($request->path(), '/'));
        if (isset($pathSegments[1]) && in_array($pathSegments[1], $this->supportedVersions)) {
            return $pathSegments[1];
        }
        
        // 4. Default version
        return $this->defaultVersion;
    }
}

// Version-aware Service Provider
namespace App\Providers;

use Illuminate\Support\ServiceProvider;
use App\Services\ApiVersionService;
use App\Http\Middleware\ApiVersionMiddleware;

class ApiVersionServiceProvider extends ServiceProvider
{
    public function register()
    {
        $this->app->singleton(ApiVersionService::class, function ($app) {
            return new ApiVersionService();
        });
        
        // Register version-specific services
        $this->app->when(\App\Http\Controllers\Api\V1\UserController::class)
                  ->needs(\App\Transformers\UserTransformerInterface::class)
                  ->give(\App\Transformers\V1\UserTransformer::class);
        
        $this->app->when(\App\Http\Controllers\Api\V2\UserController::class)
                  ->needs(\App\Transformers\UserTransformerInterface::class)
                  ->give(\App\Transformers\V2\UserTransformer::class);
    }
    
    public function boot()
    {
        // Register middleware
        $router = $this->app['router'];
        $router->aliasMiddleware('api.version', ApiVersionMiddleware::class);
        
        // Register version-aware routes
        $this->registerVersionedRoutes();
    }
    
    protected function registerVersionedRoutes()
    {
        // Content negotiation based routing
        Route::group([
            'prefix' => 'api',
            'middleware' => ['api', 'api.version'],
        ], function () {
            Route::get('users', function (Request $request) {
                $version = $request->attributes->get('api_version');
                $controllerClass = "App\\Http\\Controllers\\Api\\{$version}\\UserController";
                
                return app($controllerClass)->index();
            });
            
            Route::get('users/{user}', function (Request $request, $user) {
                $version = $request->attributes->get('api_version');
                $controllerClass = "App\\Http\\Controllers\\Api\\{$version}\\UserController";
                
                return app($controllerClass)->show($user);
            });
        });
    }
}

Query Parameter Versioning

Flexible Query-Based Versioning

Implement versioning through query parameters:

<?php

namespace App\Http\Middleware;

use Closure;
use Illuminate\Http\Request;

class QueryVersionMiddleware
{
    protected array $supportedVersions = ['1.0', '1.1', '2.0'];
    protected string $defaultVersion = '2.0';
    
    public function handle(Request $request, Closure $next)
    {
        $version = $this->determineVersion($request);
        
        // Normalize version format
        $normalizedVersion = $this->normalizeVersion($version);
        
        $request->attributes->set('api_version', $normalizedVersion);
        $request->attributes->set('raw_version', $version);
        
        $response = $next($request);
        
        // Add version info to response
        if ($response instanceof \Illuminate\Http\JsonResponse) {
            $responseData = $response->getData(true);
            $responseData['meta']['version'] = $version;
            $responseData['meta']['supported_versions'] = $this->supportedVersions;
            
            $response->setData($responseData);
        }
        
        return $response;
    }
    
    protected function determineVersion(Request $request): string
    {
        // Check query parameter
        $queryVersion = $request->query('version') ?? $request->query('v');
        
        if ($queryVersion && in_array($queryVersion, $this->supportedVersions)) {
            return $queryVersion;
        }
        
        // Check header as fallback
        $headerVersion = $request->header('API-Version');
        if ($headerVersion && in_array($headerVersion, $this->supportedVersions)) {
            return $headerVersion;
        }
        
        return $this->defaultVersion;
    }
    
    protected function normalizeVersion(string $version): string
    {
        // Convert version to controller namespace format
        return 'V' . str_replace('.', '_', $version);
    }
}

// Dynamic Controller Resolution
namespace App\Http\Controllers\Api;

use Illuminate\Http\Request;
use Illuminate\Routing\Controller;

class VersionedController extends Controller
{
    public function handleRequest(Request $request, string $resource, string $action = 'index', $id = null)
    {
        $version = $request->attributes->get('api_version', 'V2_0');
        $controllerClass = $this->resolveController($resource, $version);
        
        if (!class_exists($controllerClass)) {
            // Fall back to previous version
            $controllerClass = $this->findFallbackController($resource, $version);
        }
        
        if (!class_exists($controllerClass)) {
            return response()->json([
                'error' => 'Resource not supported in this version',
                'version' => $request->attributes->get('raw_version'),
                'resource' => $resource,
            ], 404);
        }
        
        $controller = app($controllerClass);
        
        return $this->dispatchAction($controller, $action, $request, $id);
    }
    
    protected function resolveController(string $resource, string $version): string
    {
        $resourceClass = \Str::studly($resource);
        return "App\\Http\\Controllers\\Api\\{$version}\\{$resourceClass}Controller";
    }
    
    protected function findFallbackController(string $resource, string $version): string
    {
        $versionParts = explode('_', $version);
        $major = (int) $versionParts[0];
        $minor = isset($versionParts[1]) ? (int) $versionParts[1] : 0;
        
        // Try previous minor versions
        for ($m = $minor - 1; $m >= 0; $m--) {
            $fallbackVersion = "V{$major}_{$m}";
            $controllerClass = $this->resolveController($resource, $fallbackVersion);
            
            if (class_exists($controllerClass)) {
                return $controllerClass;
            }
        }
        
        // Try previous major versions
        for ($maj = $major - 1; $maj >= 1; $maj--) {
            $fallbackVersion = "V{$maj}_0";
            $controllerClass = $this->resolveController($resource, $fallbackVersion);
            
            if (class_exists($controllerClass)) {
                return $controllerClass;
            }
        }
        
        return '';
    }
    
    protected function dispatchAction($controller, string $action, Request $request, $id = null)
    {
        switch ($action) {
            case 'index':
                return $controller->index($request);
            case 'show':
                return $controller->show($request, $id);
            case 'store':
                return $controller->store($request);
            case 'update':
                return $controller->update($request, $id);
            case 'destroy':
                return $controller->destroy($request, $id);
            default:
                if (method_exists($controller, $action)) {
                    return $controller->$action($request, $id);
                }
                
                return response()->json([
                    'error' => 'Action not supported',
                    'action' => $action,
                ], 404);
        }
    }
}

Version-Specific Transformers

Data Transformation Layer

Implement version-specific data transformers:

<?php

namespace App\Transformers;

interface TransformerInterface
{
    public function transform($data): array;
    public function transformCollection($collection): array;
}

// V1 Transformer
namespace App\Transformers\V1;

use App\Models\User;
use App\Transformers\TransformerInterface;

class UserTransformer implements TransformerInterface
{
    public function transform($user): array
    {
        if (!$user instanceof User) {
            return [];
        }
        
        return [
            'id' => $user->id,
            'name' => $user->name,
            'email' => $user->email,
            'created_at' => $user->created_at->toISOString(),
            'updated_at' => $user->updated_at->toISOString(),
            
            // V1 specific fields
            'is_active' => $user->is_active ?? true,
            'last_login' => $user->last_login_at?->toISOString(),
        ];
    }
    
    public function transformCollection($collection): array
    {
        return $collection->map(function ($item) {
            return $this->transform($item);
        })->toArray();
    }
}

// V2 Transformer
namespace App\Transformers\V2;

use App\Models\User;
use App\Transformers\TransformerInterface;

class UserTransformer implements TransformerInterface
{
    public function transform($user): array
    {
        if (!$user instanceof User) {
            return [];
        }
        
        $data = [
            'id' => $user->id,
            'uuid' => $user->uuid,
            'name' => $user->name,
            'email' => $user->email,
            'username' => $user->username,
            'email_verified_at' => $user->email_verified_at?->toISOString(),
            'created_at' => $user->created_at->toISOString(),
            'updated_at' => $user->updated_at->toISOString(),
            
            // V2 includes status object
            'status' => [
                'is_active' => $user->is_active ?? true,
                'is_verified' => !is_null($user->email_verified_at),
                'last_activity' => $user->last_activity_at?->toISOString(),
            ],
            
            // V2 includes preferences
            'preferences' => $user->preferences ?? [],
        ];
        
        // Include profile if loaded
        if ($user->relationLoaded('profile') && $user->profile) {
            $data['profile'] = [
                'bio' => $user->profile->bio,
                'avatar_url' => $user->profile->avatar_url,
                'website' => $user->profile->website,
                'location' => $user->profile->location,
            ];
        }
        
        // Include roles if loaded
        if ($user->relationLoaded('roles')) {
            $data['roles'] = $user->roles->pluck('name')->toArray();
        }
        
        return $data;
    }
    
    public function transformCollection($collection): array
    {
        return $collection->map(function ($item) {
            return $this->transform($item);
        })->toArray();
    }
}

// Transformer Factory
namespace App\Services;

use App\Transformers\TransformerInterface;

class TransformerFactory
{
    protected array $transformers = [];
    
    public function __construct()
    {
        $this->registerTransformers();
    }
    
    public function getTransformer(string $resource, string $version): ?TransformerInterface
    {
        $key = "{$resource}.{$version}";
        
        if (isset($this->transformers[$key])) {
            return app($this->transformers[$key]);
        }
        
        // Try to find fallback transformer
        return $this->findFallbackTransformer($resource, $version);
    }
    
    protected function registerTransformers(): void
    {
        $this->transformers = [
            'user.v1' => \App\Transformers\V1\UserTransformer::class,
            'user.v2' => \App\Transformers\V2\UserTransformer::class,
            'post.v1' => \App\Transformers\V1\PostTransformer::class,
            'post.v2' => \App\Transformers\V2\PostTransformer::class,
        ];
    }
    
    protected function findFallbackTransformer(string $resource, string $version): ?TransformerInterface
    {
        // Extract version number
        preg_match('/v(\d+)/', $version, $matches);
        $versionNumber = isset($matches[1]) ? (int) $matches[1] : 1;
        
        // Try previous versions
        for ($v = $versionNumber - 1; $v >= 1; $v--) {
            $fallbackKey = "{$resource}.v{$v}";
            if (isset($this->transformers[$fallbackKey])) {
                return app($this->transformers[$fallbackKey]);
            }
        }
        
        return null;
    }
}

Testing API Versions

Comprehensive Version Testing

Test all API versions thoroughly:

<?php

namespace Tests\Feature\Api;

use Tests\TestCase;
use App\Models\User;
use Illuminate\Foundation\Testing\RefreshDatabase;

class ApiVersioningTest extends TestCase
{
    use RefreshDatabase;
    
    public function test_v1_user_endpoint_structure(): void
    {
        $user = User::factory()->create();
        
        $response = $this->getJson('/api/v1/users/' . $user->id);
        
        $response->assertStatus(200)
                ->assertJsonStructure([
                    'data' => [
                        'id',
                        'name',
                        'email',
                        'created_at',
                        'updated_at',
                        'is_active',
                        'last_login',
                    ],
                    'meta' => [
                        'version',
                        'timestamp',
                    ],
                ]);
        
        $this->assertEquals('v1', $response->json('meta.version'));
    }
    
    public function test_v2_user_endpoint_structure(): void
    {
        $user = User::factory()->create();
        $user->profile()->create(['bio' => 'Test bio']);
        
        $response = $this->getJson('/api/v2/users/' . $user->id);
        
        $response->assertStatus(200)
                ->assertJsonStructure([
                    'data' => [
                        'id',
                        'uuid',
                        'name',
                        'email',
                        'username',
                        'email_verified_at',
                        'created_at',
                        'updated_at',
                        'status' => [
                            'is_active',
                            'is_verified',
                            'last_activity',
                        ],
                        'preferences',
                        'profile' => [
                            'bio',
                            'avatar_url',
                            'website',
                            'location',
                        ],
                    ],
                    'meta' => [
                        'version',
                        'timestamp',
                    ],
                ]);
        
        $this->assertEquals('v2', $response->json('meta.version'));
    }
    
    public function test_header_based_versioning(): void
    {
        $user = User::factory()->create();
        
        $response = $this->withHeaders([
            'Accept' => 'application/vnd.myapi.v1+json',
        ])->getJson('/api/users/' . $user->id);
        
        $response->assertStatus(200)
                ->assertHeader('API-Version', 'v1');
    }
    
    public function test_query_parameter_versioning(): void
    {
        $user = User::factory()->create();
        
        $response = $this->getJson('/api/users/' . $user->id . '?version=1.0');
        
        $response->assertStatus(200)
                ->assertJson([
                    'meta' => [
                        'version' => '1.0',
                    ],
                ]);
    }
    
    public function test_unsupported_version_fallback(): void
    {
        $user = User::factory()->create();
        
        $response = $this->getJson('/api/v999/users/' . $user->id);
        
        // Should fallback to latest supported version
        $response->assertStatus(200);
    }
    
    public function test_deprecation_headers(): void
    {
        $user = User::factory()->create();
        
        $response = $this->getJson('/api/v1/users/' . $user->id);
        
        $response->assertStatus(200)
                ->assertHeader('Deprecation', 'true')
                ->assertHeader('Sunset')
                ->assertHeader('Link');
    }
    
    public function test_version_specific_validation(): void
    {
        // V1 doesn't require username
        $v1Response = $this->postJson('/api/v1/users', [
            'name' => 'John Doe',
            'email' => 'john@example.com',
            'password' => 'password',
        ]);
        
        $v1Response->assertStatus(201);
        
        // V2 requires additional fields
        $v2Response = $this->postJson('/api/v2/users', [
            'name' => 'Jane Doe',
            'email' => 'jane@example.com',
            'password' => 'password',
            'username' => 'janedoe',
        ]);
        
        $v2Response->assertStatus(201);
    }
}

// Performance Testing for Versions
namespace Tests\Performance\Api;

use Tests\TestCase;
use App\Models\User;
use Illuminate\Foundation\Testing\RefreshDatabase;

class ApiVersionPerformanceTest extends TestCase
{
    use RefreshDatabase;
    
    public function test_version_resolution_performance(): void
    {
        User::factory()->count(100)->create();
        
        $startTime = microtime(true);
        
        for ($i = 0; $i < 100; $i++) {
            $this->getJson('/api/v2/users?page=' . ($i % 10 + 1));
        }
        
        $endTime = microtime(true);
        $executionTime = $endTime - $startTime;
        
        // Ensure version resolution doesn't add significant overhead
        $this->assertLessThan(5.0, $executionTime, 'Version resolution taking too long');
    }
    
    public function test_transformer_performance(): void
    {
        $users = User::factory()->count(1000)->create();
        
        $startTime = microtime(true);
        
        $response = $this->getJson('/api/v2/users?per_page=1000');
        
        $endTime = microtime(true);
        $executionTime = $endTime - $startTime;
        
        $response->assertStatus(200);
        $this->assertLessThan(2.0, $executionTime, 'V2 transformer performance degraded');
    }
}

API versioning in Laravel requires careful planning and consistent implementation. The key is choosing the right strategy for your use case and maintaining backward compatibility while enabling innovation. This approach ensures your API can evolve gracefully while serving diverse client needs in complex Laravel ecosystems.

Share this article

Add Comment

No comments yet. Be the first to comment!

More from Laravel