-
-
Notifications
You must be signed in to change notification settings - Fork 4
Exceptions
Jean-Marc Strauven edited this page Dec 23, 2025
·
1 revision
Learn about the exceptions thrown by Laravel ApiRoute and how to handle them.
Laravel ApiRoute throws specific exceptions for API version errors:
| Exception | When Thrown |
|---|---|
VersionNotFoundException |
Requested version doesn't exist |
VersionSunsetException |
Version has reached end-of-life |
InvalidVersionException |
Version format is invalid |
ApiRouteException |
Base exception class |
Thrown when the requested API version doesn't exist.
// Request to non-existent version
GET /api/v99/users → VersionNotFoundException<?php
namespace Grazulex\ApiRoute\Exceptions;
class VersionNotFoundException extends ApiRouteException
{
public function __construct(
public readonly string $requestedVersion
) {
parent::__construct("API version '{$requestedVersion}' not found.");
}
public function render(Request $request): JsonResponse
{
return response()->json([
'error' => 'version_not_found',
'message' => $this->getMessage(),
'requested_version' => $this->requestedVersion,
'available_versions' => ApiRoute::versions()->pluck('name'),
], 404);
}
}{
"error": "version_not_found",
"message": "API version 'v99' not found.",
"requested_version": "v99",
"available_versions": ["v1", "v2", "v3"]
}HTTP Status: 404 Not Found
Thrown when a sunset version is accessed and rejection is configured.
// Version with past sunset date + 'reject' action
GET /api/v1/users → VersionSunsetException (if v1 is sunset)// config/apiroute.php
'sunset' => [
'action' => 'reject', // Only throws when 'reject'
'status_code' => 410,
],<?php
namespace Grazulex\ApiRoute\Exceptions;
class VersionSunsetException extends ApiRouteException
{
public function __construct(
public readonly VersionDefinition $version
) {
parent::__construct("API version '{$version->name()}' has been sunset.");
}
public function render(Request $request): JsonResponse
{
$config = config('apiroute');
$migrationGuides = $config['documentation']['migration_guides'] ?? [];
return response()->json([
'error' => 'api_version_sunset',
'message' => "API version {$this->version->name()} is no longer available.",
'sunset_date' => $this->version->sunsetDate()?->toIso8601String(),
'successor' => $this->version->successor(),
'migration_guide' => $migrationGuides[$this->version->name()] ?? null,
], $config['sunset']['status_code'] ?? 410);
}
}{
"error": "api_version_sunset",
"message": "API version v1 is no longer available.",
"sunset_date": "2024-12-01T00:00:00+00:00",
"successor": "v2",
"migration_guide": "https://docs.example.com/migration/v1-to-v2"
}HTTP Status: 410 Gone (configurable)
Thrown when the version format is invalid.
// Invalid version format
X-API-Version: invalid → InvalidVersionException{
"error": "invalid_version",
"message": "Invalid API version format: 'invalid'"
}HTTP Status: 400 Bad Request
Base exception class for all package exceptions.
<?php
namespace Grazulex\ApiRoute\Exceptions;
use Exception;
class ApiRouteException extends Exception
{
// Base class for all ApiRoute exceptions
}try {
// API logic
} catch (ApiRouteException $e) {
// Handle any ApiRoute exception
}In Laravel 11+ (bootstrap/app.php):
use Grazulex\ApiRoute\Exceptions\VersionNotFoundException;
use Grazulex\ApiRoute\Exceptions\VersionSunsetException;
->withExceptions(function (Exceptions $exceptions) {
$exceptions->render(function (VersionNotFoundException $e, Request $request) {
return response()->json([
'error' => 'version_not_found',
'message' => $e->getMessage(),
'help' => 'Visit /api/versions for available versions',
], 404);
});
$exceptions->render(function (VersionSunsetException $e, Request $request) {
// Custom sunset response
return response()->json([
'error' => 'api_version_sunset',
'message' => 'Please upgrade to a newer API version',
'upgrade_url' => 'https://docs.example.com/upgrade',
], 410);
});
})In Laravel 10 (app/Exceptions/Handler.php):
<?php
namespace App\Exceptions;
use Grazulex\ApiRoute\Exceptions\VersionNotFoundException;
use Grazulex\ApiRoute\Exceptions\VersionSunsetException;
use Illuminate\Foundation\Exceptions\Handler as ExceptionHandler;
class Handler extends ExceptionHandler
{
public function register(): void
{
$this->renderable(function (VersionNotFoundException $e, $request) {
return response()->json([
'error' => 'version_not_found',
'message' => $e->getMessage(),
], 404);
});
$this->renderable(function (VersionSunsetException $e, $request) {
return response()->json([
'error' => 'version_sunset',
'message' => 'This API version is no longer available.',
], 410);
});
}
}Create a custom error response format:
<?php
namespace App\Http\Responses;
class ApiErrorResponse
{
public static function make(
string $error,
string $message,
int $status,
array $extra = []
): JsonResponse {
return response()->json([
'success' => false,
'error' => [
'code' => $error,
'message' => $message,
'status' => $status,
...$extra,
],
'timestamp' => now()->toIso8601String(),
], $status);
}
}$exceptions->render(function (VersionSunsetException $e, Request $request) {
return ApiErrorResponse::make(
error: 'api_version_sunset',
message: $e->getMessage(),
status: 410,
extra: [
'successor' => $e->version->successor(),
'migration_guide' => config("apiroute.documentation.migration_guides.{$e->version->name()}"),
]
);
});// config/apiroute.php
'sunset' => [
'status_code' => 410, // HTTP Gone (default)
],| Code | Meaning | When to Use |
|---|---|---|
410 Gone |
Resource permanently removed (default) | Standard sunset |
404 Not Found |
Resource doesn't exist | Legacy clients |
426 Upgrade Required |
Client must upgrade | Force migration |
$exceptions->render(function (ApiRouteException $e, Request $request) {
Log::warning('API version error', [
'error' => get_class($e),
'message' => $e->getMessage(),
'path' => $request->path(),
'ip' => $request->ip(),
'user_agent' => $request->userAgent(),
]);
// Let default rendering handle response
return null;
});test('returns 404 for non-existent version', function () {
$response = $this->getJson('/api/v99/users');
$response->assertStatus(404);
$response->assertJson([
'error' => 'version_not_found',
]);
});test('returns 410 for sunset version', function () {
// Setup sunset version
ApiRoute::version('v1', fn() => Route::get('users', fn() => 'v1'))
->sunset(now()->subDay());
$response = $this->getJson('/api/v1/users');
$response->assertStatus(410);
$response->assertJson([
'error' => 'api_version_sunset',
]);
});test('sunset exception contains version info', function () {
$version = new VersionDefinition('v1', fn() => null);
$version->sunset(now()->subDay());
$version->setSuccessor('v2');
$exception = new VersionSunsetException($version);
expect($exception->version->name())->toBe('v1');
expect($exception->version->successor())->toBe('v2');
});use Grazulex\ApiRoute\Facades\ApiRoute;
// In API gateway or middleware
if (!ApiRoute::hasVersion($requestedVersion)) {
return redirect()->route('api.versions');
}
if (ApiRoute::isSunset($requestedVersion)) {
return redirect()->route('api.upgrade', ['from' => $requestedVersion]);
}- Events - React to version events
- Middleware - Where exceptions are thrown
- Configuration - Configure error behavior
Laravel ApiRoute - Complete API versioning lifecycle management for Laravel
Home | Getting Started | Examples | Configuration
Made with ❤️ for the Laravel community