DEV Community

Cover image for Laravel 8 - API Versioning ⭐⭐⭐⭐⭐
DaleLanto
DaleLanto

Posted on • Edited on

Laravel 8 - API Versioning ⭐⭐⭐⭐⭐

What is “API Versioning”?

API versioning is the process of creating new versions of an API whenever some big changes is necessary that could break the existing version of the API and ruin all the sites, apps or systems connected to it.

It has become very common in recent years, the main idea behind API versions is to never delete the old versions of our code, whenever we need to make big changes in our system, specifically when this changes could create problems with integrations (Softwares who are consuming our APIs).

Following this idea we will have endpoints like this:

https://www.website.com/api/v1/users
https://www.website.com/api/v2/users
Enter fullscreen mode Exit fullscreen mode

Note that the difference between the endpoints is “v1” and “v2”, they are related to the version we are using.

By implementing it this way, the old integrations (apps, external Softwares, Front-end applications,…) could keep using the first version (v1) while the new version integrations could just use version 2 (v2).

Following this principle, we do not have worry about updates or new features breaking our clients integrations.

How to apply versioning to folder?

The first thing to do is to organize the folders.

Create a folder called Api inside app/Http/Controllers, then create two folders called V1 and V2 inside app/Http/Controllers/Api, you should have something like this:

app/Http/Controllers/Api/V1
app/Http/Controllers/Api/V2
Enter fullscreen mode Exit fullscreen mode

it will look like this

/app
  /controllers
    /Api
      /v1
        /UserController.php
      /v2
        /UserController.php
Enter fullscreen mode Exit fullscreen mode

Create controllers for v1 version:
php artisan make:controller Api/V1/MyController

Create jsonResource for v1 version
php artisan make:resource V1/UserResource

Example how to create jsonResource for v2 version
php artisan make:resource V2/UserResource

The idea is that each controller uses a different resource, the first controller version should look like this:

<?php
namespace App\Http\Controllers\Api\V1;

use App\Models\User;

class UserController
{
    public function getUser(int $idUser)
    {
        $user = User::find($idUser);

        return new \App\Http\Resources\V1\User($user);
    }
}
Enter fullscreen mode Exit fullscreen mode

In the second version, we just need to use a different resource. The same idea could be replicated for Models, Views, Services, Repositories, or whatever layer you could be using.

Load Controller according to the version (URL)

To do that we will use the Laravel middleware.

First of all, open the file config/app.php in your favorite editor, add the following lines of code:

/*
|-------------------------------------------
| Current API Version
|-------------------------------------------
*/

'api_latest'  => '2',
Enter fullscreen mode Exit fullscreen mode

Now, we need to create our middleware, execute the following command on your terminal:
php artisan make:middleware APIVersion

Open the middleware created (app\Http\Middleware\APIVersion.php) and add the code:

<?php
namespace App\Http\Middleware;use Closure;

/**
 * Class APIVersion
 * @package App\Http\Middleware
 */
class APIVersion
{
    /**
     * Handle an incoming request.
     *
     * @param  Request $request
     * @param  Closure $next
     *
     * @return mixed
     */
    public function handle($request, Closure $next, $guard)
    {
        config(['app.api.version' => $guard]);
        return $next($request);
    }
}
Enter fullscreen mode Exit fullscreen mode

This middleware will be responsible for intercepting the requests and apply the version requested by the clients.

Don't forget to register it on your app/Http/Kernel.php file:

protected $routeMiddleware = [
    // ...
    'api_version' => App\Http\Middleware\APIversion::class,
];
Enter fullscreen mode Exit fullscreen mode

Open the file ../app/Providers/RouteServiceProvider.php and add this attribute to the class:

/** @var string $apiNamespace */
protected $apiNamespace ='App\Http\Controllers\Api';
Enter fullscreen mode Exit fullscreen mode

Inside the method mapApiRoutes, add the following code:

/**
 * Define the "api" routes for the application.
 *
 * These routes are typically stateless.
 *
 * @return void
 */
protected function mapApiRoutes()
{
    Route::group([
        'middleware' => ['api', 'api_version:v1'],
        'namespace'  => "{$this->apiNamespace}\V1",
        'prefix'     => 'api/v1',
    ], function ($router) {
        require base_path('routes/api_v1.php');
    });

    Route::group([
        'middleware' => ['api', 'api_version:v2'],
        'namespace'  => "{$this->apiNamespace}\V2",
        'prefix'     => 'api/v2',
    ], function ($router) {
        require base_path('routes/api_v2.php');
    });
}
Enter fullscreen mode Exit fullscreen mode

In that way, we will separate the route groups in different files, look closely! we are using the PHP method require to load:

routes/api_v1.php
routes/api_v2.php
Enter fullscreen mode Exit fullscreen mode

It will be necessary to create two different files manually after you just need to follow the same idea on the file routes/api.phpcreated by Laravel on the setup of new projects.

The original file will not be necessary since now. It keeps the routes more organized when your project grows. After that, you just need to create your controllers and routes.

Hurray! you learned how to keep your APIs/projects more organized, mainly thinking about the future of your application.

Top comments (5)

Collapse
 
uniqueginun profile image
uniqueginun

I don't see where this
config(['app.api.version' => $guard]);
used??

and why do we need middleware??

Collapse
 
naeemijaz profile image
Naeem Ijaz

Sir Where to add these following code of mapApiRoutes

Collapse
 
aymanelshehawy profile image
Ayman Zayed Ali

call it inside boot function

public function boot()
    {
        $this->configureRateLimiting();
        $this->routes(function () {
            Route::middleware('api')
                ->prefix('api')
                ->group(base_path('routes/api.php'));
            Route::middleware('web')
                ->group(base_path('routes/web.php'));
            $this->mapApiRoutes();
        });
    }
Enter fullscreen mode Exit fullscreen mode
Collapse
 
manuelli profile image
Sebastian Manuelli

Add them in /app/Providers/RouteServiceProvider.php

Collapse
 
efcjunior profile image
Everson Junior

Hi!

What's purpose of 'api_latest' => '2' ?