appkr / api
RESTful HTTP API dev tool for Laravel or Lumen based project
Installs: 14 944
Dependents: 0
Suggesters: 0
Security: 0
Stars: 31
Watchers: 5
Forks: 6
Open Issues: 0
Requires
- league/fractal: 0.16.*
- shrikeh/teapot: 2.3.*
Requires (Dev)
- phpunit/phpunit: 6.1.*
README
INDEX
- 1. ABOUT
- 2. FEATURE
- 3. LARAVEL/LUMEN IMPLEMENTATION EXAMPLE
- 4. HOW TO INSTALL
- 5. CONFIG
- 6. TRANSFORMER
- 7. NESTING SUB-RESOURCE
- 8. APIs
- 9. BUNDLED EXAMPLE
-
- LICENSE & CONTRIBUTION
-
- CHANGELOG
1. ABOUT
A lightweight RESTful API builder for Laravel or/and Lumen project.
2. FEATURE
- Provides Laravel/Lumen Service Provider for the
league/fractal
. - Provides configuration capability for the library.
- Provides easy way of making transformed/serialized API response.
- Provides
make:transformer
artisan command. - Provides examples, so that users can quickly copy & paste into his/her project.
3. LARAVEL/LUMEN IMPLEMENTATION EXAMPLE(How to use)
3.1. API Endpoint
Define RESTful resource route in Laravel way.
<?php // app/Http/routes.php OR routes/web.php OR routes/api.php Route::group(['prefix' => 'v1'], function () { Route::resource( 'books', 'BooksController', ['except' => ['create', 'edit']] ); });
Lumen doesn't support RESTful resource route. You have to define them one by one.
<?php // app/Http/routes.php OR routes/web.php OR routes/api.php $app->group(['prefix' => 'v1'], function ($app) { $app->get('books', [ 'as' => 'v1.books.index', 'uses' => 'BooksController@index', ]); $app->get('books/{id}', [ 'as' => 'v1.books.show', 'uses' => 'BooksController@show', ]); $app->post('books', [ 'as' => 'v1.books.store', 'uses' => 'BooksController@store', ]); $app->put('books/{id}, [ 'as' => 'v1.books.update', 'uses' => 'BooksController@update', ]); $app->delete('books/{id}', [ 'as' => 'v1.books.destroy', 'uses' => 'BooksController@destroy', ]); });
3.2. Controller
The subsequent code block is the controller logic for /v1/books/{id}
endpoint. Note the use cases of json()
helper and transformer on the following code block.
<?php // app/Http/Controllers/BooksController.php namespace App\Http\Controllers\V1; use App\Http\Controllers\Controller; use App\Book; use App\Transformers\BookTransformer; use Illuminate\Http\Request; class BooksController extends Controller { public function index() { return json()->withPagination( Book::latest()->paginate(5), new BookTransformer ); } public function store(Request $request) { // Assumes that validation is done at somewhere else return json()->created( $request->user()->create($request->all()) ); } public function show($id) { return json()->withItem( Book::findOrFail($id), new BookTransformer ); } public function update(Request $request, $id) { $book = Book::findOrFail($id); return ($book->update($request->all())) ? json()->success('Updated') : json()->error('Failed to update'); } public function destroy($id) { $book = Book::findOrFail($id); return ($book->delete()) ? json()->success('Deleted') : json()->error('Failed to delete'); } }
4. HOW TO INSTALL
4.1. Composer.
$ composer require "appkr/api: 1.*"
4.2. Add the service provider.
<?php // config/app.php (Laravel) 'providers' => [ Appkr\Api\ApiServiceProvider::class, ];
<?php // boostrap/app.php (Lumen) $app->register(Appkr\Api\ApiServiceProvider::class);
4.3. [OPTIONAL] Publish assets.
# Laravel only $ php artisan vendor:publish --provider="Appkr\Api\ApiServiceProvider"
The configuration file is located at config/api.php
.
In Lumen we can manually create config/api.php
file, and then activate the configuration at bootstrap/app.php
like the following.
<?php // bootstrap/app.php (Lumen) $app->register(Appkr\Api\ApiServiceProvider::class); $app->configure('api');
Done !
5. CONFIG
Skim through the config/api.php
, which is inline documented.
6. TRANSFORMER
6.1. What?
For more about what the transformer is, what you can do with this, and why it is required, see this page. 1 transformer for 1 model is a best practice(e.g. BookTransformer
for Book
model).
6.2. Transformer Boilerplate Generator
Luckily this package ships with an artisan command that conveniently generates a transformer class.
$ php artisan make:transformer {subject} {--includes=}
# e.g. php artisan make:transformer "App\Book" --includes="App\\User:author,App\\Comment:comments:true"
-
subject
_ The string name of the model class. -
includes
_ Sub-resources that is related to the subject model. By providing this option, your API client can have control over the response body. see NESTING SUB RESOURCES section.The option's signature is
--include=Model,eloquent_relationship_methods[,isCollection]
.If the include-able sub-resource is a type of collection, like
Book
andComment
relationship in the example, we providetrue
as the third value of the option.
Note
We should always use double back slashes (
\\
), when passing a namespace in artisan command WITHOUT quotation marks.$ php artisan make:transformer App\\Book --includes=App\\User:author,App\\Comment:comments:true
A generated file will look like this:
<?php // app/Transformers/BookTransformer.php namespace App\Transformers; use App\Book; use Appkr\Api\TransformerAbstract; use League\Fractal; use League\Fractal\ParamBag; class BookTransformer extends TransformerAbstract { /** * List of resources possible to include using url query string. * * @var array */ protected $availableIncludes = [ 'author', 'comments' ]; /** * Transform single resource. * * @param \App\Book $book * @return array */ public function transform(Book $book) { $payload = [ 'id' => (int) $book->id, // ... 'created' => $book->created_at->toIso8601String(), 'link' => [ 'rel' => 'self', 'href' => route('api.v1.books.show', $book->id), ], ]; return $this->buildPayload($payload); } /** * Include author. * This method is used, when an API client request /v1/books?include=author * * @param \App\Book $book * @param \League\Fractal\ParamBag|null $params * @return \League\Fractal\Resource\Item */ public function includeAuthor(Book $book, ParamBag $params = null) { return $this->item( $book->author, new \App\Transformers\UserTransformer($params) ); } /** * Include comments. * This method is used, when an API client request /v1/books??include=comments * * @param \App\Book $book * @param \League\Fractal\ParamBag|null $params * @return \League\Fractal\Resource\Collection */ public function includeComments(Book $book, ParamBag $params = null) { $transformer = new \App\Transformers\CommentTransformer($params); $comments = $book->comments() ->limit($transformer->getLimit()) ->offset($transformer->getOffset()) ->orderBy($transformer->getSortKey(), $transformer->getSortDirection()) ->get(); return $this->collection($comments, $transformer); } }
7. NESTING SUB-RESOURCES
An API client can request a resource with its sub-resource. The following example is requesting authors
list. At the same time, it requests each author's books
list. It also has additional parameters, which reads as 'I need total of 3 books for this author when ordered by recency without any skipping'.
GET /authors?include=books:limit(3|0):sort(id|desc)
When including multiple sub resources,
GET /authors?include[]=books:limit(2|0)&include[]=comments:sort(id|asc) # or alternatively GET /authors?include=books:limit(2|0),comments:sort(id|asc)
In case of deep recursive nesting, use dot (.
). In the following example, we assume the publisher model has relationship with somethingelse model.
GET /books?include=author,publisher.somethingelse
8. APIs
The following is the full list of response methods that Appkr\Api\Http\Response
provides. Really handy when making a json response in a controller.
8.1. Appkr\Api\Response
- Available Methods
<?php // Generic response. // If valid callback parameter is provided, jsonp response can be provided. // This is a very base method. All other responses are utilizing this. respond(array $payload); // Respond collection of resources // If $transformer is not given as the second argument, // this class does its best to transform the payload to a simple array withCollection( \Illuminate\Database\Eloquent\Collection $collection, \League\Fractal\TransformerAbstract|null $transformer, string|null $resourceKey // for JsonApiSerializer only ); // Respond single item withItem( \Illuminate\Database\Eloquent\Model $model, \League\Fractal\TransformerAbstract|null $transformer, string|null $resourceKey // for JsonApiSerializer only ); // Respond collection of resources with pagination withPagination( \Illuminate\Contracts\Pagination\LengthAwarePaginator $paginator, \League\Fractal\TransformerAbstract|null $transformer, string|null $resourceKey // for JsonApiSerializer only ); // Respond json formatted success message // api.php provides configuration capability success(string|array $message); // Respond 201 // If an Eloquent model is given at an argument, // the class tries its best to transform the model to a simple array created(string|array|\Illuminate\Database\Eloquent\Model $primitive); // Respond 204 noContent(); // Respond 304 notModified(); // Generic error response // This is another base method. Every other error responses use this. // If an instance of \Exception is given as an argument, // this class does its best to properly format a message and status code error(string|array|\Exception|null $message); // Respond 401 // Note that this actually means unauthenticated unauthorizedError(string|array|null $message); // Respond 403 // Note that this actually means unauthorized forbiddenError(string|array|null $message); // Respond 404 notFoundError(string|array|null $message); // Respond 405 notAllowedError(string|array|null $message); // Respond 406 notAcceptableError(string|array|null $message); // Respond 409 conflictError(string|array|null $message); // Respond 410 goneError(string|array|null $message); // Respond 422 unprocessableError(string|array|null $message); // Respond 500 internalError(string|array|null $message); // Set http status code // This method is chain-able setStatusCode(int $statusCode); // Set http response header // This method is chain-able setHeaders(array $headers); // Set additional meta data // This method is chain-able setMeta(array $meta);
8.2. Appkr\Api\TransformerAbstract
- Available Methods
<?php // We can apply this method against an instantiated transformer, // to get the parsed query parameters that belongs only to the current resource. // // e.g. GET /v1/author?include[]=books:limit(2|0)&include[]=comments:sort(id|asc) // $transformer = new BookTransformer; // $transformer->get(); // Will produce all parsed parameters: // // [ // // 'limit' => 2 // if not given default value at config // // 'offset' => 0 // if not given default value at config // // 'sort' => 'created_at' // if given, given value // // 'order' => 'desc' // if given, given value // // ] // Alternatively we can pass a key. // $transformer->get('limit'); // Will produce limit parameter: // // 2 get(string|null $key) // Exactly does the same function as get. // Was laid here, to enhance readability. getParsedParams(string|null $key)
8.3. helpers.php
- Available Functions
<?php // Make JSON response // Returns Appkr\Api\Http\Response object if no argument is given, // from there you can chain any public apis that are listed above. json(array|null $payload) // Determine if the current framework is Laravel is_laravel(); // Determine if the current framework is Lumen is_lumen(); // Determine if the current request is for API endpoints, and expecting API response is_api_request(); // Determine if the request is for update is_update_request(); // Determine if the request is for delete is_delete_request();
9. BUNDLED EXAMPLE
The package is bundled with a set of example that follows the best practices. It includes:
- Database migrations and seeder
- routes definition, Eloquent Model and corresponding Controller
- FormRequest (Laravel only)
- Transformer
- Integration Test
Follow the guide to activate and test the example.
9.1. Activate examples
Uncomment the line.
<?php // vendor/appkr/api/src/ApiServiceProvider.php $this->publishExamples();
9.2. Migrate and seed tables
Do the following to make test table and seed test data. Highly recommend to use SQLite, to avoid polluting the main database of yours.
$ php artisan migrate --path="vendor/appkr/api/src/example/database/migrations" --database="sqlite" $ php artisan db:seed --class="Appkr\Api\Example\DatabaseSeeder" --database="sqlite"
9.3. See it works
Boot up a server.
$ php artisan serve
Head on to GET /v1/books
, and you should see a well formatted json response. Try each route to get accustomed to, such as /v1/books=include=authors
, /v1/authors=include=books:limit(2|0):order(id|desc)
.
9.4. [OPTIONAL] Run integration test
# Laravel
$ vendor/bin/phpunit vendor/appkr/api/src/example/BookApiTestForLaravel.php
# Lumen
$ vendor/bin/phpunit vendor/appkr/api/src/example/BookApiTestForLumen.php
Note
If you finished evaluating the example, don't forget to rollback the migration and re-comment the unnecessary lines at
ApiServiceProvider
.
10. LICENSE & CONTRIBUTION
MIT License. Issues and PRs are always welcomed.
11. CHANGELOG
v3.0.1
- Supports auto package discovery in Laravel 5.5 (No need to add ServiceProvider in config/app.php)
v3.0.0
- API not changed.
- Update
league/fractal
version to 0.16.0
v2.3.4
jsonEncodeOption
config added.
v2.3.3
Appkr\Api\Http\UnexpectedIncludesParamException
will be thrown instead ofUnexpectedValueException
when includes query params are not valid.
v2.3.0
withCollection()
now acceptsIlluminate\Support\Collection
.- Fix bug at
SimpleArrayTransformer
.
v2.2.0
- Field name converting to snake or camel case depending on configuration (
config('api.convert.key')
). - Date format converting depending on configuration (
config('api.convert.date')
).
v2.1.0
TransformerAbstract::buildPayload
method added to filter the list of response fields (Backward compatible).- Artisan generated transformer template changed (Backward compatible).
v2.0.0
TransformerAbstract
's API changed.- Partial response by query string feature removed. Instead we can explicitly set the list of attributes to respond in a Transformer's
$visible
or$hidden
property.
v1.1.0 [buggy]
- Field grouping feature added for partial response conveniences.
TransformerAbstract
now throwsUnexpectedValueException
instead ofException
, when params or values passed by API client are not acceptable.
12. SPONSORS
- Thanks JetBrains for supporting phpStorm IDE.