Courses

How to Build Laravel 12 API From Scratch

Versioning your API: from V1 to V2 and Beyond

Summary of this lesson:
- Implementing API versioning
- Organizing controllers and routes by version
- Managing different API versions simultaneously

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.

Previous: Generating API Documentation with OpenAPI Swagger
avatar

Should I also versionning all files related to the api like FormRequest, Ressource, migration, Policies, Observers and so on...?

avatar

It depends.

FormRequest, resources -> Yes. I would version them myself.

Policies -> If there are big changes between them - yeah. You should be already versioning contrllers and other things around, so why not.

Migrations -> No, as that would mean a separate database. Just make sure nothing crashes for older users

Observers -> Maybe. This one is tricky to answer exactly.

But all in all - you have to keep in mind that you have to support older versions of the code. So whatever you do, you have to check if it makes sense. For example, removing a function from anywhere - can be pretty bad. So a version with all the things - is needed. But for an additional field - it might not be so important.

avatar
You can use Markdown
avatar
You can use Markdown