Api Resources
, (*1)
Manage your resources maintaining API versioning. With a simple middleware separate routes by api version, and smart instanciate Http\Resources based on this version., (*2)
Add the middleware 'api.v:2' on your api/v2 group., (*3)
And then api_resource('App\User')->make($user) is the same as new App\Http\Resources\App\v2\User($user), but version free., (*4)
App\Http\Resources\
|- App\
|- v1\
|- User.php
|- v2\
|- Rank.php
|- User.php
The idea behing this
A while back I faced this API versioning problem, so I wrote this medium post with my solution and this package reflects this., (*5)
Installation
You can install this package via composer using:, (*6)
composer require juampi92/api-resources
The package will automatically register itself., (*7)
Config
To publish the config file to config/api.php run:, (*8)
php artisan vendor:publish --provider="Juampi92\APIResources\APIResourcesServiceProvider"
This will publish a file api.php in your config directory with the following content:, (*9)
return [
/*
|--------------------------------------------------------------------------
| API Version
|--------------------------------------------------------------------------
|
| This value is the latest version of your api. This is used when
| there's no specified version on the routes, so it will take this as the
| default, or latest.
*/
'version' => '1',
/*
|--------------------------------------------------------------------------
| Resources home path
|--------------------------------------------------------------------------
|
| This value is the base folder where your resources are stored.
| When using multiple APIs, you can leave it as a string if every
| api is in the same folder, or as an array with the APIs as keys.
*/
'resources_path' => 'App\Http\Resources',
/*
|--------------------------------------------------------------------------
| Resources
|--------------------------------------------------------------------------
|
| Here is the folder that has versioned resources. If you store them
| in the root of 'resources_path', leave this empty or null.
*/
'resources' => 'App'
];
Middleware
Install this middleware on your Http/Kernel.php under the $routeMiddleware, (*10)
protected $routeMiddleware = [
...
'api.v' => \Juampi92\APIResources\Middleware\APIversion::class,
...
];
For this package to work, you need to understand how it requires resources., (*11)
If we have the following config:, (*12)
[
'version' => '2',
'resources_path' => 'App\Http\Resources',
'resources' => 'Api'
]
This means that if you include the Api\User resource, it will instantiate App\Http\Resources\Api\v2\User., (*13)
Api works for sub organizing your structure, but you can put your Resources versionate folders in the root, like this:, (*14)
[
'version' => '2',
'resources_path' => 'App\Http\Resources',
'resources' => ''
]
Now if we include User, it will instantiate App\Http\Resources\v2\User., (*15)
Fallback
When you use a version that is NOT the latest, if you try to include a Resource that's NOT defined inside that version's directory, this will automatically fallback in the LATEST version., (*16)
This way you don't have to duplicate new resources on previous versions., (*17)
Usage
Middleware
When you group your API routes, you should now apply the middleware api.v into the group like this:, (*18)
// App v1 API
Route::group([
'middleware' => ['app', 'api.v:1'],
'prefix' => 'api/v1',
], function ($router) {
require base_path('routes/app_api.v1.php');
});
// App v2 API
Route::group([
'middleware' => ['app', 'api.v:2'],
'prefix' => 'api/v2',
], function ($router) {
require base_path('routes/app_api.v2.php');
});
That way, if you use the Facade, you can check the current version by doing APIResource::getVersion() and will return the version specified on the middleware., (*19)
Facade
There are many ways to create resources. You can use the Facade accessor:, (*20)
use Juampi92\APIResources\Facades\APIResource;
class SomethingController extends Controller {
...
public function show(Something $model)
{
return APIResource::resolve('App\Something')->make($model);
}
}
Global helper
class SomethingController extends Controller {
...
public function show(Something $model)
{
return api_resource('App\Something')->make($model);
}
}
Collections
Instead of make, use collection for arrays, just like Laravel's documentation., (*21)
class SomethingController extends Controller {
...
public function index()
{
$models = Something::all();
return api_resource('App\Something')->collection($models);
}
}
If you wanna use a ResourceCollection, you might wanna rewrite the collects() method., (*22)
class UserCollection extends ResourceCollection
{
protected function collects()
{
return APIResource::resolveClassname('App\User');
}
}
This way, the ResourceCollection will always have the correct class., (*23)
resolveClassname will try to use the current version of the class, but if it's not possible, will use the latest., (*24)
Nested resources
To take advantage of the fallback functionality, it's recomended to use api_resource inside the resources. This way you preserve the right version, or the latest if it's not defined., (*25)
class Post extends Resource {
public function toArray($request)
{
return [
'title' => $this->title,
...
'user' => api_resource('App\User')->make($this->user);
];
}
}
Multiple APIs
There might be the case where you have more than one API living on the same project, but using diferent versions. This app supports that.
First, the config/api.php, (*26)
return [
'default' => 'api',
'version' => [
'api' => '2',
'desktop' => '3'
],
'resources_path' => 'App\Http\Resources'
// Or one path each
'resources_path' => [
'api' => 'App\Http\Resources',
'desktop' => 'Vendorname\ExternalPackage\Resources'
],
'resources' => [
'api' => 'Api',
'desktop' => ''
],
];
Then, you need to configure the middleware. Instead of using api.v:1, you now have to specify the name: api.v:3,desktop., (*27)
Then the rest works as explained before., (*28)
API Route
Sometimes you must return a route url on the api response.
If you wanna keep the api version (which is always the current version), api-resources has the solution for you., (*29)
// When defining the routes
Route::group([
'middleware' => ['app', 'api.v:1'],
'prefix' => 'api/v1',
// Using name on a group will prefix it.
'name' => 'api.v1.',
], function ($router) {
Route::get('/auth/login', [
// This will be api.v1.auth.login
'name' => 'auth.login',
'use' => '...',
]);
});
With this we have api.v1.auth.login and api.v2.auth.login when creating a new version., (*30)
Now just do api_route('api.auth.login'), and it will output /api/v1/auth/login or /api/v2/auth/login accordingly., (*31)
How it works
It's grabbing the config api.resources and doing a strtolower, so if you have 'resources' => 'App', will transform app.auth.login into app.v1.auth.login.
If you need to customize it, add a new config entry in config/api.php like this:, (*32)
/*
|--------------------------------------------------------------------------
| Route prefix
|--------------------------------------------------------------------------
|
| By default, the route prefix is the lowercase resources folder.
| So it'd be `app.v1.auth.login` has the prefix `app`.
|
| Using `app` will do api_route(`app.auth.login`) => `app.v?.auth.login`.
|
*/
'route_prefix' => 'app'
If works with multiple APIs as explained before., (*33)
Testing
Run the tests with:, (*34)
vendor/bin/phpunit
Credits
License
The MIT License (MIT). Please see License File for more information., (*35)