Learn to implement robust API versioning in Laravel using route prefixes and namespaced controllers to ensure backward compatibility as your application evolves.
Previously in this course, we covered Database Factories and Seeding to ensure our testing environment remains consistent. Now, we shift our focus to the lifecycle of our API. As your project board application grows, you will inevitably need to change data structures or response formats. If you push these changes to your live production endpoint, you will break every client currently consuming your data.
Effective API versioning is the professional standard for managing these transitions. It allows you to maintain multiple concurrent versions of your API, giving your users time to migrate while you iterate on new features.
When we first built our REST API with Sanctum, we assumed a single, static contract between our server and our clients. However, real-world software is never static.
Versioning provides a safety net. By isolating changes, you protect your routing logic from becoming a tangled mess of if/else statements that check for user-agent strings or custom headers. A clean versioning strategy keeps your codebase modular and your resource controllers focused on a single schema definition.
The most common and readable approach in Laravel is URI versioning. We define our versions explicitly within the routes/api.php file (or a dedicated route file).
Instead of flat routes, we use route groups to prefix our endpoints. Here is how we structure our project board's task endpoints:
PHP#6A9955">// routes/api.php use Illuminate\Support\Facades\Route; #6A9955">// Version 1(Legacy) Route::prefix('v1')->group(function () { Route::get('/tasks', [App\Http\Controllers\Api\V1\TaskController::class, 'index']); }); #6A9955">// Version 2(Current) Route::prefix('v2')->group(function () { Route::get('/tasks', [App\Http\Controllers\Api\V2\TaskController::class, 'index']); });
By explicitly declaring the namespace or the full class path, we ensure that v1 and v2 endpoints can coexist, even if they share the same URI path.
As you can see above, we point our routes to different controller namespaces. This is crucial. If you try to share a single controller for multiple versions, you will quickly find yourself adding conditional logic that makes the code unreadable and hard to test.
Instead, create a directory structure that mirrors your versions:
TEXTapp/Http/Controllers/Api/ ├── V1/ │ └── TaskController.php └── V2/ └── TaskController.php
When you need to update the TaskController for v2, you can refactor or extend the logic without touching the v1 controller. If v2 is mostly the same as v1, your V2\TaskController can simply extend the V1\TaskController and override only the methods that require change.
Imagine we want to change how we return the task's due date. In v1, we returned a simple string. In v2, we want to return an object with both the formatted date and a localized timestamp.
V1 Controller:
PHPnamespace App\Http\Controllers\Api\V1; class TaskController extends Controller { public function index() { return Task::all()->map(fn($task) => [ 'id' => $task->id, 'due' => $task->due_at->toDateTimeString(), ]); } }
V2 Controller:
PHPnamespace App\Http\Controllers\Api\V2; use App\Http\Controllers\Api\V1\TaskController as BaseController; class TaskController extends BaseController { public function index() { return Task::all()->map(fn($task) => [ 'id' => $task->id, 'due' => [ 'formatted' => $task->due_at->diffForHumans(), 'iso' => $task->due_at->toIso8601String(), ], ]); } }
This approach maintains maintainability by keeping the legacy logic untouched while providing a clean path forward for new features.
app/Http/Controllers/Api/ named V1 and V2.TaskController into the V1 folder and update its namespace.TaskController in V2 that returns a slightly different JSON structure for the index method.routes/api.php to use the new route groups and ensure both /api/v1/tasks and /api/v2/tasks return their respective responses.TaskResourceV1 and TaskResourceV2.By isolating your changes within versioned namespaces, you ensure that your API remains a stable, predictable platform for your users, even as your underlying domain model evolves.
Up next: We will implement advanced request filtering and sorting, allowing your API consumers to query data more efficiently.
Master API versioning and maintain backward compatibility in your distributed systems. Learn to implement header-based versioning for clean, scalable APIs.
Read moreMaster stateless API authentication in Laravel. Learn to issue and verify JWTs, implement secure token rotation, and handle revocation in a high-traffic system.
API Versioning Strategies