SyntaxStudy
Sign Up
Laravel API Versioning and Error Handling
Laravel Beginner 1 min read

API Versioning and Error Handling

API versioning allows you to evolve your API without breaking existing clients. The most common approach in Laravel is URL versioning, where routes are grouped under a version prefix like /api/v1/ and /api/v2/. Each version has its own controller namespace and resource classes. Route groups in routes/api.php or separate version-specific route files handle the separation cleanly. Laravel's exception handler in app/Exceptions/Handler.php is the central place to customize API error responses. The register() method uses renderable() callbacks to intercept specific exception types and return standardized JSON. ModelNotFoundException returns a 404, ValidationException returns a 422 with field errors, and AuthorizationException returns a 403. A consistent error envelope with a message key and an errors key for validation makes clients predictable. Throttling protects APIs from abuse. Laravel's built-in throttle middleware applies rate limits using the token bucket algorithm. Customizable rate limiters are defined in RouteServiceProvider (or a dedicated service provider) using RateLimiter::for(). They can vary limits per user, per plan, or based on request properties. The response includes X-RateLimit-Limit and X-RateLimit-Remaining headers automatically.
Example
<?php
// routes/api.php — versioned API groups
use Illuminate\Support\Facades\Route;

Route::prefix('v1')->name('api.v1.')->group(function () {
    require base_path('routes/api_v1.php');
});

Route::prefix('v2')->name('api.v2.')->group(function () {
    require base_path('routes/api_v2.php');
});

// app/Exceptions/Handler.php — custom API error responses
namespace App\Exceptions;

use Illuminate\Foundation\Exceptions\Handler as ExceptionHandler;
use Illuminate\Database\Eloquent\ModelNotFoundException;
use Illuminate\Auth\AuthenticationException;
use Throwable;

class Handler extends ExceptionHandler
{
    public function register(): void
    {
        $this->renderable(function (ModelNotFoundException $e, $request) {
            if ($request->expectsJson()) {
                return response()->json(['message' => 'Resource not found.'], 404);
            }
        });

        $this->renderable(function (AuthenticationException $e, $request) {
            if ($request->expectsJson()) {
                return response()->json(['message' => 'Unauthenticated.'], 401);
            }
        });
    }
}