API versioning using Laravel’s Resources

Juampi Barreto
5 min readDec 7, 2017

--

Maintaining API versioning can be difficult. Specially if you have core changes.

I’m not gonna cover the heavy API change, when also the database and core models are changed, but the ones that make a change so significant that the Application Programming Interface should adapt.

TL;DR: Dynamically instantiating Resources to format the json response, depending on the version of the API, but leaving everything else (controllers, models) as one version.

Update: Since publication I decided to build a composer package to abstract this behavior. I’d like for you to also read this article, as it explains the logic behind it and a glimpse of how it does work.

https://github.com/juampi92/api-resources

Feel free to use it, give it a star, fork it, and send PRs 🙂

Introduction

Leaving aside this model/database breaking changes API, we usually have modifications on the controllers.

Imagine we have a Business, that has a location. On the API/v1, the location was a string, but now you’d like to store street, number, lat, long, city. You could change your json response using location_street, location_number, …etc, and will not break the original implementation of location:string, but that’d be nasty, cause now location is an object.

First step is to create a new v2 of the API. Probably you will have to add a new set of routes to the RouteServiceProvider, containing the exact same copy of previous routes, and copy every controller to fit in the following folder structure:
app/Http/Controllers/App/v2

Then we will migrate the database, add those fields, then update the model and the way it’s edited on the controller. Probably you will have to live with the idea that API/v1 users will not have a way to input location’s coordinates, but you will allow these fields to new API/v2 users.

So now we are changing the inputs of the controller. So it’s easy cause we are using a duplicated controller, add that simple logic (grab the other location’s parameters), and fill the model, and then return the new business json structure, having the location as an object.

But wait…

If we take a closer look, the only thing that has really changed was the response. The only thing that will break previous API users is the json output, cause the input was also optional on the model.

If there was a way of telling the controller to respond something if it’s API/v1, and something else if it’s API/v2… that’d solve a lot the code duplication, and make the versioning process way simpler.

So let’s make a way of changing the response, and to keep the same controller, that will work for both versions now.

Here’s when the new Laravel’s 5.5 API Resources are really handy.

API Resources

(official doc)

The idea is for now to have versioned resources, so that’d be like

app/Http/Resources/Api/v1/Business.php
app/Http/Resources/Api/v1/BusinessCollection.php
app/Http/Resources/Api/v2/Business.php
app/Http/Resources/Api/v2/BusinessCollection.php

We are now gonna focus on the Business.php. The first implementation should be something like

app/Http/Resources/Api/v1/Business.php

public function toArray($request)
{
return [
'id' => $this->id,
'name' => $this->name,
'location'=> $this->location_street . ' ' .
$this->location_number,
];
}

app/Http/Resources/Api/v2/Business.php

public function toArray($request)
{
return [
'id' => $this->id,
'name' => $this->name,
'location' => [
'street' => $this->location_street,
'number' => $this->location_number,
'lat' => $this->location_lat,
'lng' => $this->location_lng,
],
];
}

Look. Same Model, same controller, but different output.

The Implementation

To achieve this, we need to: first, make the routes aware of the api_version, then change the way we initiate the resources, and use the route’s api_version on this.

So, we’ll do this by creating a middleware which simple function will be set the api_version.

config/app.php

/*
|-------------------------------------------
| API Version
|-------------------------------------------
|
| This value is the version of your api.
| It's used when there's no specified
| version on the routes, so it will take this
| as the default, or current.
*/

'api_latest' => '2',

Then create a middleware, alias api.version

<?php

namespace App\Http\Middleware;

use Closure;

class APIversion
{
/**
* Handle an incoming request.
*
* @param Request $request
* @param Closure $next
*
* @return mixed
*/
public function handle($request, $next, $guard)
{
config(['app.api_version' => $guard]);

return $next($request);
}
}

Note that api_version is not api_latest. This way we could keep track of the latest API always. So we apply those to the routes:

// App v1 API
Route::group([
'middleware' => ['app', 'api.version:1'],
'namespace' => 'App\Http\Controllers\App',
'prefix' => 'api/v1',
], function ($router) {
require base_path('routes/app_api.v1.php');
});

// App v2 API
Route::group([
'middleware' => ['app', 'api.version:2'],
'namespace' => 'App\Http\Controllers\App',
'prefix' => 'api/v2',
], function ($router) {
require base_path('routes/app_api.v2.php');
});

Now to the Resource construction. We now know the api_version of the request by accessing config(‘app.api_version’). If it’s null, we could assume it’s equal to config(‘app.api_latest’).
So what I did on my project, was adapting my APIController. Now I’m able to instantiate Resources using the syntax:

$response = $this->resource('Api\Business', Business::find($id));
return $this->respond($response)->send();

So we add this two methods on every controller.
Note: You can use traits, or add this methods into a generic APIController that every controller extends from.

/**
* @param string $resourceName
* @param array ...$args
*
* @return object
*/
public function resource($resourceName, ...$args)
{
// Get's the request's api version, or the latest if undefined
$v = config('app.api_version', config('app.api_latest'));

$className = $this->getResourceClassname($resourceName, $v);
$class = new \ReflectionClass($className);
return $class->newInstanceArgs($args);
}

/**
* Parse Api\BusinessResource for
* App\Http\Resources\Api\v{$v}\BusinessResource
*
* @param string $className
* @param string $v
*
* @return string
*/
protected function getResourceClassname($className, $v)
{
$re = '/.*\\\\(.*)/';
return 'App\Http\Resources\\' .
preg_replace($re, 'Api\\v' . $v . '\\\$1', $className);
}

We replace the App\Business for App\vX\Business, and using ReflectionClass we create an instance with the rest of the arguments.

Bonus Track

If we add these small lines of code:

if (!class_exists($className)) {
$className = $this->getResourceClassname($resourceName,
config('app.api_latest'));
}

We can check if that class exists, and if not, we use the app.api_latest.
So that way, we only keep in v1 the Resources that have changed on v2, and we prevent more file duplication.

Conclusion

I know this might not be your regular API versioning procedure, but if you are maintaining a controllable API, for example your own mobile app, that has updates but not everyone updates at the same time, this versioning is quite helpful, cause I lets you modify the code as one, but keep the old interfaces, at least for a while.

Then you can send a deprecated exception on every sub-route, and that way you should remove the old Resources, remove the routes file, and just place one route that returns that exception.

If you find this interesting, you can follow me on Medium and Twitter @juampi_92.

--

--

Responses (4)