leamasuero
leamasuero

Reputation: 361

Api versioning in laravel: routing depending on the "Accept" Header

Since I want to fire endpoints upon accept header, I created a middleware to identify which version is using the client:

// ApiVersionMiddleware

public function handle($request, Closure $next)
{
    $route = $request->route();
    $actions = $route->getAction();

    $actions['uses'] = str_replace(
        '{api}',
        $request->header('api-version'),
        $actions['uses']
    );

    $route->setAction($actions);

    return $next($request);
}

I then changed the default RouteServiceProvider namespace:

class RouteServiceProvider extends ServiceProvider {

//  protected $namespace = 'App\Http\Controllers'; // default value
    protected $namespace = null;
        .
        .     
}

Finally, in my routes.php file, I have:

Route::group(['middleware' => 'api-version'], function()
{
    Route::resource('items', 'App\Http\Controllers\{api}\ItemsController', ['only' => ['show', 'index', 'store', 'destroy']]);
});

I tried to register the middleware in the HttpKernel class instead of grouping route definitions but it did not work.

I was wondering if there is a better way to achieve API versioning.

Thanks.

Upvotes: 5

Views: 3526

Answers (2)

zhangzhen
zhangzhen

Reputation: 51

For anyone who just want some simple code. This is inside the default RouteServiceProvider. Just add a few lines.

$this->routes(function () {
            Route::middleware('web')
                ->namespace($this->namespace)
                ->group(base_path('routes/web.php'));

            $max_api_version = 6; // how many files should be loaded
            $all_headers = getallheaders(); // request object is not available. use php function
            $client_version =  intval($all_headers['Api-Version']??1);
            for ($i = 1; $i <= $max_api_version; $i++){
                $file_path = base_path('routes/api_v'.$i.'.php');
                if ($client_version >= $i && file_exists($file_path)){
                    Route::prefix('api')
                        ->group($file_path);
                }
            }
        });

You can't have two identical route in laravel. Laravel will use $method.$domain.$uri to determine same routes and later ones will simply override previous ones. So loading different version routes in ascending order with the above code will result in:

  1. if client version is 3. Then api_v1,api_v2,api_v3 are loaded in ascending order. And api_v4, api_v5... is not loaded.
  2. if an uri appears in 1,3,10 and client version is 6. Then the route in v3 will be used since it overrides the one in v1 and not been overrided by v10 because v10 is not even loaded.
  3. if an uri appears in 4,5 and client version is 2. Then it's just a 404 since v4 and v5 are not loaded.

Upvotes: 0

astroanu
astroanu

Reputation: 3971

I guess you can try something like this on your routes.php I haven't tested but should work

switch (Request::header('api-version')) {
    case 'v2':
        include_once('routes_v2.php');
        break;

    default:
        include_once('routes_v1.php');
}

And in the route_v....php files define the routes as you would do normally.

UPDATE

The correct way to do this would be in the RouteServiceProvider's map function. You will have just two route files.

You can easily manage multiple versions of apis in the dingo/api package. It will save a lot of time for you.

Upvotes: 5

Related Questions