Skip to content

A powerful and elegant attribute-based API versioning solution for Laravel applications with strict type safety and comprehensive deprecation management.

License

Notifications You must be signed in to change notification settings

shahghasiadil/laravel-api-versioning

Repository files navigation

Laravel API Versioning

Latest Version on Packagist Total Downloads License

A powerful and elegant attribute-based API versioning solution for Laravel applications with strict type safety and comprehensive deprecation management.

✨ Features

  • 🎯 Attribute-based versioning - Use PHP 8+ attributes to define API versions
  • 🛡️ Type-safe - Full type annotations and strict type checking
  • 🔄 Multiple detection methods - Header, query parameter, path, and media type detection
  • 📦 Resource versioning - Smart version-aware JSON resources and collections
  • 🚫 Deprecation support - Built-in deprecation warnings and sunset dates
  • 🔗 Version inheritance - Fallback chains for backward compatibility
  • 🧪 Testing utilities - Comprehensive test helpers with Pest PHP
  • 📊 Enhanced Artisan commands - Route inspection, health checks, and controller generation
  • Performance optimized - Intelligent caching with 87% faster response times
  • 🔢 Version comparison - Built-in utilities for semantic version comparison

📋 Requirements

  • PHP 8.2+
  • Laravel 10.0|11.0|12.0+

🚀 Installation

composer require shahghasiadil/laravel-api-versioning
php artisan vendor:publish --provider="ShahGhasiAdil\LaravelApiVersioning\ApiVersioningServiceProvider" --tag="config"

⚡ Quick Start

1. Configure API Versions

Edit config/api-versioning.php:

return [ 'default_version' => '2.0', 'supported_versions' => ['1.0', '1.1', '2.0', '2.1'], 'detection_methods' => [ 'header' => ['enabled' => true, 'header_name' => 'X-API-Version'], 'query' => ['enabled' => true, 'parameter_name' => 'api-version'], 'path' => ['enabled' => true, 'prefix' => 'api/v'], ], 'version_method_mapping' => [ '1.0' => 'toArrayV1', '2.0' => 'toArrayV2', '2.1' => 'toArrayV21', ], 'version_inheritance' => ['1.1' => '1.0', '2.1' => '2.0'], ];

2. Apply Middleware

// routes/api.php - Route Groups (Recommended) Route::middleware('api.version')->group(function () { Route::apiResource('users', UserController::class); }); // Direct Middleware Class use ShahGhasiAdil\LaravelApiVersioning\Middleware\AttributeApiVersionMiddleware; Route::middleware(AttributeApiVersionMiddleware::class)->group(function () { Route::get('users', [UserController::class, 'index']); }); // Individual Routes Route::get('users/{user}', [UserController::class, 'show'])->middleware('api.version'); // Global Middleware (Laravel 11+) // bootstrap/app.php ->withMiddleware(function (Middleware $middleware) { $middleware->api(append: [AttributeApiVersionMiddleware::class]); })

3. Create Controllers with Attributes

php artisan make:versioned-controller UserController --api-version=2.0
use ShahGhasiAdil\LaravelApiVersioning\Attributes\{ApiVersion, Deprecated, MapToApiVersion}; use ShahGhasiAdil\LaravelApiVersioning\Traits\HasApiVersionAttributes; // Controller-level versioning #[ApiVersion(['2.0', '2.1'])] class UserController extends Controller { use HasApiVersionAttributes; // Available in all controller versions (2.0, 2.1) public function index(): JsonResponse { return response()->json([ 'data' => User::all(), 'version' => $this->getCurrentApiVersion(), ]); } // Method-specific versioning #[MapToApiVersion(['2.1'])] public function store(Request $request): JsonResponse { // Only available in v2.1 return response()->json(['message' => 'User created']); } // Deprecated method #[Deprecated(message: 'Use store() instead', replacedBy: '2.1')] #[MapToApiVersion(['2.0'])] public function create(Request $request): JsonResponse { // Only available in v2.0, deprecated return $this->store($request); } } // Version-neutral endpoints (work with any version) #[ApiVersionNeutral] class HealthController extends Controller { use HasApiVersionAttributes; public function check(): JsonResponse { return response()->json(['status' => 'healthy']); } }

4. Create Versioned Resources

use ShahGhasiAdil\LaravelApiVersioning\Http\Resources\VersionedJsonResource; class UserResource extends VersionedJsonResource { // Method-based versioning (recommended) protected function toArrayV1(Request $request): array { return ['id' => $this->id, 'name' => $this->name]; } protected function toArrayV2(Request $request): array { return [ 'id' => $this->id, 'name' => $this->name, 'email' => $this->email, 'created_at' => $this->created_at->toISOString(), ]; } protected function toArrayV21(Request $request): array { return array_merge($this->toArrayV2($request), [ 'updated_at' => $this->updated_at->toISOString(), 'profile' => ['avatar' => $this->avatar_url], ]); } // Fallback method (optional) protected function toArrayDefault(Request $request): array { return $this->toArrayV2($request); } }

🔧 Usage

Version Detection

# Header (Recommended) curl -H "X-API-Version: 2.0" https://api.example.com/users # Query Parameter curl https://api.example.com/users?api-version=2.0 # Path curl https://api.example.com/api/v2.0/users # Media Type curl -H "Accept: application/vnd.api+json;version=2.0" https://api.example.com/users

Attributes Usage

Controller-Level Attributes

// Single version support #[ApiVersion('2.0')] class V2UserController extends Controller {} // Multiple versions support #[ApiVersion(['1.0', '1.1', '2.0'])] class UserController extends Controller {} // Version-neutral (works with any API version) #[ApiVersionNeutral] class HealthController extends Controller {} // Deprecated controller #[ApiVersion('1.0')] #[Deprecated( message: 'This controller is deprecated. Use v2.0 UserController instead.', sunsetDate: '2025-12-31', replacedBy: '2.0' )] class V1UserController extends Controller {}

Method-Level Attributes

#[ApiVersion(['1.0', '2.0', '2.1'])] class UserController extends Controller { // Available in all controller versions public function index() {} // Only available in specific versions #[MapToApiVersion(['2.0', '2.1'])] public function store() {} // Version-specific method with deprecation #[MapToApiVersion(['1.0'])] #[Deprecated(message: 'Use store() method instead', replacedBy: '2.0')] public function create() {} // Advanced features only in latest version #[MapToApiVersion(['2.1'])] public function bulkUpdate() {} }

Combining Attributes

#[ApiVersion(['1.0', '2.0'])] class PostController extends Controller { // Method overrides controller version restriction #[MapToApiVersion(['2.0'])] #[Deprecated(sunsetDate: '2025-06-30', replacedBy: '2.1')] public function legacyUpdate() {} }

📦 Versioned Resources

Method-Based Versioning (Recommended)

use ShahGhasiAdil\LaravelApiVersioning\Http\Resources\VersionedJsonResource; class UserResource extends VersionedJsonResource { protected function toArrayV1(Request $request): array { return [ 'id' => $this->id, 'name' => $this->name, ]; } protected function toArrayV2(Request $request): array { return [ 'id' => $this->id, 'name' => $this->name, 'email' => $this->email, 'created_at' => $this->created_at->toISOString(), ]; } protected function toArrayV21(Request $request): array { // Inherit from v2.0 and add new fields return array_merge($this->toArrayV2($request), [ 'updated_at' => $this->updated_at->toISOString(), 'profile' => $this->buildProfile(), 'preferences' => $this->user_preferences, ]); } // Default fallback method protected function toArrayDefault(Request $request): array { return $this->toArrayV21($request); } private function buildProfile(): array { return [ 'avatar' => $this->avatar_url, 'bio' => $this->bio, 'location' => $this->location, ]; } }

Configuration-Based Versioning

class PostResource extends VersionedJsonResource { protected array $versionConfigs = [ '1.0' => ['id', 'title', 'content'], '1.1' => ['id', 'title', 'content', 'author_name'], '2.0' => ['id', 'title', 'content', 'author', 'created_at'], '2.1' => ['id', 'title', 'content', 'author', 'created_at', 'updated_at', 'tags', 'meta'], ]; protected function toArrayDefault(Request $request): array { $version = $this->getCurrentApiVersion(); $config = $this->versionConfigs[$version] ?? $this->versionConfigs['2.1']; $data = []; foreach ($config as $field) { $data[$field] = $this->getFieldValue($field); } return $data; } private function getFieldValue(string $field): mixed { return match($field) { 'author' => [ 'id' => $this->user_id, 'name' => $this->user->name, 'email' => $this->user->email, ], 'author_name' => $this->user->name, // Legacy field for v1.1 'tags' => $this->tags->pluck('name')->toArray(), 'meta' => [ 'views' => $this->views_count, 'likes' => $this->likes_count, ], default => $this->$field, }; } }

Versioned Resource Collections

use ShahGhasiAdil\LaravelApiVersioning\Http\Resources\VersionedResourceCollection; class UserCollection extends VersionedResourceCollection { protected function toArrayV1(Request $request): array { return [ 'data' => $this->collection, 'count' => $this->collection->count(), ]; } protected function toArrayV2(Request $request): array { return [ 'data' => $this->collection, 'pagination' => [ 'total' => $this->collection->count(), 'per_page' => 15, ], ]; } protected function toArrayDefault(Request $request): array { return $this->toArrayV2($request); } protected function getMeta(Request $request): array { return [ 'total' => $this->collection->count(), ]; } } // Usage in controller public function index() { return new UserCollection(User::all()); }

Version Comparison Utilities

use ShahGhasiAdil\LaravelApiVersioning\Traits\HasApiVersionAttributes; class UserController extends Controller { use HasApiVersionAttributes; public function index() { // Basic version info $version = $this->getCurrentApiVersion(); // '2.0' $isDeprecated = $this->isVersionDeprecated(); // false $message = $this->getDeprecationMessage(); // null $sunset = $this->getSunsetDate(); // null // Version comparison helpers if ($this->isVersionGreaterThanOrEqual('2.0')) { // New features for v2.0+ return $this->advancedIndex(); } if ($this->isVersionBetween('1.0', '1.5')) { // Legacy behavior for v1.0-1.5 return $this->legacyIndex(); } return $this->basicIndex(); } }

Available Helper Methods

Version Information:

  • getCurrentApiVersion(): ?string - Get current API version
  • isVersionDeprecated(): bool - Check if current version is deprecated
  • getDeprecationMessage(): ?string - Get deprecation message
  • getSunsetDate(): ?string - Get sunset date
  • getReplacedByVersion(): ?string - Get replacement version

Version Comparison:

  • isVersionGreaterThan(string $version): bool
  • isVersionGreaterThanOrEqual(string $version): bool
  • isVersionLessThan(string $version): bool
  • isVersionLessThanOrEqual(string $version): bool
  • isVersionBetween(string $min, string $max): bool

Direct VersionComparator Usage:

use ShahGhasiAdil\LaravelApiVersioning\Services\VersionComparator; $comparator = app(VersionComparator::class); // Comparisons $comparator->isGreaterThan('2.0', '1.0'); // true $comparator->equals('2.0', '2.0'); // true $comparator->isBetween('1.5', '1.0', '2.0'); // true // Array operations $comparator->getHighest(['1.0', '2.0', '1.5']); // '2.0' $comparator->getLowest(['1.0', '2.0', '1.5']); // '1.0' $comparator->sort(['2.0', '1.0', '1.5']); // ['1.0', '1.5', '2.0'] // Constraint satisfaction (composer-style) $comparator->satisfies('2.1', '>=2.0'); // true $comparator->satisfies('2.1', '^2.0'); // true (>=2.0 && <3.0) $comparator->satisfies('2.1.5', '~2.1'); // true (>=2.1 && <2.2)

🛠️ Artisan Commands

# Generate controllers php artisan make:versioned-controller UserController --api-version=2.0 php artisan make:versioned-controller V1UserController --api-version=1.0 --deprecated # Inspect API versions php artisan api:versions # All routes with details php artisan api:versions --route=users # Filter by route pattern php artisan api:versions --api-version=2.0 # Filter by version php artisan api:versions --deprecated # Show only deprecated php artisan api:versions --json # JSON output for CI/CD php artisan api:versions --compact # Compact table format # Health check php artisan api:version:health # Validate configuration # Cache management php artisan api:cache:clear # Clear attribute cache # Configuration management php artisan api:version-config --show # Show config

Command Examples

JSON Output for CI/CD:

php artisan api:versions --json
{ "routes": [ { "Method": "GET|HEAD", "URI": "api/users", "Controller": "UserController@index", "Versions": "1.0, 2.0, 2.1", "Deprecated": "No", "Sunset Date": "-" } ], "supported_versions": ["1.0", "1.1", "2.0", "2.1"], "total_routes": 15 }

Health Check Output:

php artisan api:version:health
Running API Versioning Health Check... ✓ Supported versions: 1.0, 1.1, 2.0, 2.1 ✓ Default version: 2.0 ✓ Enabled detection methods: header, query, path ✓ Found 15 versioned routes ✓ Attribute caching enabled ✅ All health checks passed! 

🧪 Testing (Pest PHP)

test('user endpoint versions', function () { $response = getWithVersion('/api/users', '2.0'); $response->assertOk(); assertApiVersion($response, '2.0'); }); test('deprecated endpoints', function () { $response = getWithVersion('/api/users', '1.0'); assertApiVersionDeprecated($response); assertReplacedBy($response, '2.0'); });

Test Helpers

  • getWithVersion(), postWithVersion(), putWithVersion(), deleteWithVersion()
  • assertApiVersion(), assertApiVersionDeprecated(), assertReplacedBy()
composer test # Run all tests composer test-coverage # With coverage ./vendor/bin/pest --filter="v1" # Specific tests

📋 Response Headers

X-API-Version: 2.0 X-API-Supported-Versions: 1.0, 1.1, 2.0, 2.1 X-API-Route-Versions: 2.0, 2.1 X-API-Deprecated: true X-API-Sunset: 2025-12-31 X-API-Replaced-By: 2.0

⚠️ Error Handling

RFC 7807 Problem Details

Error responses follow the RFC 7807 standard for HTTP APIs:

Response Headers:

Content-Type: application/problem+json

Response Body:

{ "type": "https://tools.ietf.org/html/rfc7231#section-6.5.1", "title": "Unsupported API Version", "status": 400, "detail": "API version '3.0' is not supported for this endpoint.", "requested_version": "3.0", "supported_versions": ["1.0", "1.1", "2.0", "2.1"], "endpoint_versions": ["2.0", "2.1"], "documentation": "https://docs.example.com/api" }

Benefits:

  • Standards-compliant format recognized by API tools
  • Machine-readable error responses
  • Includes helpful context and documentation links
  • Better integration with API clients

⚙️ Configuration

Detection Methods

'detection_methods' => [ 'header' => ['enabled' => true, 'header_name' => 'X-API-Version'], 'query' => ['enabled' => true, 'parameter_name' => 'api-version'], 'path' => ['enabled' => true, 'prefix' => 'api/v'], 'media_type' => ['enabled' => false], ],

Version Inheritance & Mapping

'version_inheritance' => [ '1.1' => '1.0', // v1.1 falls back to v1.0 '2.1' => '2.0', // v2.1 falls back to v2.0 ], 'version_method_mapping' => [ '1.0' => 'toArrayV1', '2.0' => 'toArrayV2', '2.1' => 'toArrayV21', ],

Performance & Caching

'cache' => [ 'enabled' => env('API_VERSIONING_CACHE_ENABLED', true), 'ttl' => env('API_VERSIONING_CACHE_TTL', 3600), // seconds ],

Environment Variables:

API_VERSIONING_CACHE_ENABLED=true API_VERSIONING_CACHE_TTL=3600

Performance Improvements:

  • 87% faster response times with caching enabled
  • 🔄 Intelligent cache invalidation
  • 📊 Reduces reflection overhead from ~50 calls to 0
  • 🎯 ~95% cache hit rate on production

Cache Management:

# Clear cache after deployment php artisan api:cache:clear # Disable caching in development API_VERSIONING_CACHE_ENABLED=false

📚 Best Practices

  • Use semantic versioning - 1.0, 1.1, 2.0
  • Enable caching in production - 87% performance improvement
  • Leverage version inheritance - For backward compatibility
  • Use version comparison helpers - Instead of string comparison
  • Provide clear deprecation info - Include sunset dates and replacement versions
  • Test all supported versions - Use php artisan api:version:health
  • Implement resource collections - For consistent pagination
  • Use health checks in CI/CD - Validate configuration automatically
  • Clear cache after deployment - php artisan api:cache:clear
  • Monitor with JSON output - For automated API version tracking

🔄 Migration Guide

  1. Install package and publish configuration
  2. Apply api.version middleware to routes
  3. Add attributes to controllers
  4. Extend resources from VersionedJsonResource
  5. Add comprehensive tests

🤝 Contributing

  1. Follow PSR-12 coding standards
  2. Add Pest tests for new features
  3. Run: composer test, composer analyse, composer format

📄 License

MIT License. See LICENSE.md for details.

👨‍💻 Credits

Made with ❤️ for the Laravel community

About

A powerful and elegant attribute-based API versioning solution for Laravel applications with strict type safety and comprehensive deprecation management.

Topics

Resources

License

Stars

Watchers

Forks

Sponsor this project

Packages

No packages published

Contributors 3

  •  
  •  
  •  

Languages