Skip to content

Migration v1 to v2

Jean-Marc Strauven edited this page Jan 2, 2026 · 1 revision

Migration Guide: v1.x to v2.0

This guide helps you migrate from laravel-apiroute v1.x to v2.0.


Breaking Changes

Version Declaration

v2.0 introduces configuration-based version registration as the recommended approach. This change ensures versions are properly registered on every application boot, solving the issue where versions were lost between tests.

Before (v1.x)

// routes/api.php or RouteServiceProvider::boot()
ApiRoute::version('v1', function () {
    Route::get('users', [UserController::class, 'index']);
})->current();

After (v2.0 - Recommended)

// config/apiroute.php
'versions' => [
    'v1' => [
        'routes' => base_path('routes/api/v1.php'),
        'middleware' => ['auth:sanctum'],
        'status' => 'active',
    ],
],
// routes/api/v1.php
Route::get('users', [UserController::class, 'index']);

Why This Change?

In v1.x, when running tests, Laravel creates a new application container between tests. The ApiRouteManager singleton was recreated with an empty versions collection, and the RouteServiceProvider::boot() method (where ApiRoute::version() was called) was not re-executed.

The configuration-based approach ensures versions are loaded from config on every boot(), making it work reliably in all scenarios including:

  • Feature tests with RefreshDatabase
  • Parallel testing
  • Queue workers
  • Octane applications

Step-by-Step Migration

1. Create Route Files

Create a dedicated file for each API version:

routes/
├── api/
│   ├── v1.php
│   ├── v2.php
│   └── v3.php

Move your route definitions from the closure to these files:

// routes/api/v1.php
<?php

use App\Http\Controllers\Api\V1\UserController;
use Illuminate\Support\Facades\Route;

Route::apiResource('users', UserController::class);

2. Update Configuration

Add the versions section to your config/apiroute.php:

'versions' => [
    'v1' => [
        'routes' => base_path('routes/api/v1.php'),
        'middleware' => ['auth:sanctum'],
        'status' => 'deprecated',
        'deprecated_at' => '2025-06-01',
        'sunset_at' => '2025-12-01',
        'successor' => 'v2',
        'documentation' => 'https://docs.myapp.com/api/v1',
        'rate_limit' => 100,
    ],
    'v2' => [
        'routes' => base_path('routes/api/v2.php'),
        'middleware' => ['auth:sanctum'],
        'status' => 'active',
        'documentation' => 'https://docs.myapp.com/api/v2',
        'rate_limit' => 1000,
    ],
    'v3' => [
        'routes' => base_path('routes/api/v3.php'),
        'middleware' => ['auth:sanctum'],
        'status' => 'beta',
    ],
],

3. Remove Old Route Registration

Remove the ApiRoute::version() calls from your routes/api.php or RouteServiceProvider:

// Before: routes/api.php
ApiRoute::version('v1', function () {
    Route::apiResource('users', UserController::class);
})->current();

// After: routes/api.php
// Empty or with non-versioned routes

4. Republish Configuration

If you've previously published the config, you may need to update it:

php artisan vendor:publish --tag="apiroute-config" --force

Then merge your existing settings with the new versions section.


Configuration Reference

Version Options

Option Type Description
routes string Path to route file (required)
middleware array Additional middleware
status string active, beta, deprecated, sunset
deprecated_at string|null Deprecation date (Y-m-d)
sunset_at string|null Sunset date (Y-m-d)
successor string|null Successor version name
documentation string|null Documentation URL
rate_limit int|null Requests per minute

Backward Compatibility

The ApiRoute::version() method still works but is not recommended for new projects. If you're using it, be aware that:

  1. Versions registered via ApiRoute::version() may not persist between tests
  2. You may need to call ApiRoute::version() in your test setup

Using Legacy Approach in Tests

If you must use the legacy approach, reset and reload versions in your test setup:

beforeEach(function () {
    // Reset the manager
    app(ApiRouteManager::class)->reset();

    // Re-register versions
    ApiRoute::version('v1', function () {
        Route::get('test', fn () => response()->json(['ok' => true]));
    })->current();

    // Boot to apply config versions (if any)
    app(ApiRouteManager::class)->boot();
});

FAQ

Can I mix both approaches?

Yes, but it's not recommended. Configuration-based versions are loaded first, then ApiRoute::version() calls add additional versions. However, the imperative versions will still have the persistence issue in tests.

Do I need to change my controllers?

No, controllers remain unchanged. Only how you register routes changes.

What about route naming?

Route naming works the same way. Define names in your route files:

// routes/api/v1.php
Route::get('users', [UserController::class, 'index'])->name('api.v1.users.index');

Next Steps

Clone this wiki locally