If you are working on a project long enough, you will need versioning at some point. If you have used any APIs like Stripe or Facebook, they have version numbers. Knowing which version you use is essential because it may be a part of the URL endpoint or some parameter. So, how do you implement versioning in your project?
Until now, we had endpoint /api/categories
without any version. Let's create version two, a copy of version one.
To do that, first, let's move the existing API to version one. Create an app\Http\Controllers\Api\V1
directory and move CategoryController
and ProductController
inside it. Change the namespace in both Controllers.
namespace App\Http\Controllers\Api; namespace App\Http\Controllers\Api\V1; // ...
Next, change the Controller path in the routes/api.php
.
routes/api.php:
Route::get('/user', function (Request $request) { return $request->user();})->middleware('auth:sanctum'); Route::apiResource('categories', \App\Http\Controllers\Api\CategoryController::class) Route::apiResource('categories', \App\Http\Controllers\Api\V1\CategoryController::class) ->middleware('auth:sanctum'); Route::get('products', [\App\Http\Controllers\Api\ProductController::class, 'index']); Route::get('products', [\App\Http\Controllers\Api\V1\ProductController::class, 'index']);
Last, we must change the prefix from /api
to /api/v1
. Routes are configured in the bootstrap/app.php
the withRouting
method.
app/Providers/RouteServiceProvider.php:
return Application::configure(basePath: dirname(__DIR__)) ->withProviders() ->withRouting( web: __DIR__.'/../routes/web.php', api: __DIR__.'/../routes/api.php', commands: __DIR__.'/../routes/console.php', health: '/up', apiPrefix: 'api/v1', ) ->withMiddleware(function (Middleware $middleware) { $middleware->statefulApi(); }) ->withExceptions(function (Exceptions $exceptions) { // })->create();
Instead of /api
, we have /api/v1
endpoint for the API routes.
Next, let's add an endpoint for version two. Create an app\Http\Controllers\Api\V2
directory and copy both Controllers inside. Change the namespace for both Controllers.
namespace App\Http\Controllers\Api\V1; namespace App\Http\Controllers\Api\V2; // ...
Create a new Route file api_v2.php
inside routes
folder. Put the same Routes for version one but change the Controllers from version one to two.
routes/api_v2.php:
Route::get('/user', function (Request $request) { return $request->user();})->middleware('auth:sanctum'); Route::apiResource('categories', \App\Http\Controllers\Api\V2\CategoryController::class) ->middleware('auth:sanctum'); Route::get('products', [\App\Http\Controllers\Api\V2\ProductController::class, 'index']);
And last, we must register this new api_v2.php
Route file in the bootstrap/app.php
the withRouting
method.
app/Providers/RouteServiceProvider.php:
use Illuminate\Support\Facades\Route; return Application::configure(basePath: dirname(__DIR__)) ->withProviders() ->withRouting( web: __DIR__.'/../routes/web.php', api: __DIR__.'/../routes/api.php', commands: __DIR__.'/../routes/console.php', health: '/up', apiPrefix: 'api/v1', then: function () { Route::prefix('api/v2')->group(function () { require __DIR__.'/../routes/api_v2.php'; }); }, ) ->withMiddleware(function (Middleware $middleware) { $middleware->statefulApi(); }) ->withExceptions(function (Exceptions $exceptions) { // })->create();
Let's add a simple where to check if the version two endpoint uses the correct Controller.
app/Http/Controllers/V2/CategoryController.php:
class CategoryController extends Controller{ public function index() { abort_if(! auth()->user()->tokenCan('categories-list'), 403); return CategoryResource::collection(Category::all()); return CategoryResource::collection(Category::where('id', '<', 3)->get()); } // ...}
As we can see, when using version 2 for the endpoint, we get only two categories with fewer than three IDs.
If you want to learn more about Laravel API versioning, we have an article Laravel API Versioning: All You Need To Know About V1/V2.