johnylemon / laravel-apidocs
Laravel API documentation generating tool
Installs: 2 355
Dependents: 0
Suggesters: 0
Security: 0
Stars: 47
Watchers: 3
Forks: 3
Open Issues: 2
Requires
- php: ^7.3 || ^8.0
- laravel/framework: ^6.0 || ^7.0 || ^8.0
Requires (Dev)
- orchestra/testbench: ^6.6
- phpunit/phpunit: ^9.5
README
The problem
I don't like writing tons of lines of stupid annotations just to have hope that api documentation will be generated correctly without errors that says nothing. And I am not the only one. More.
This package solves this problem the way I like - by writing PHP code.
The solution
This package adds apidocs
method to Laravel routes, where you can define route definitions using code you use every day.
This way you can:
- reuse, extend and modify existing api definitions
- create generic endpoint definitions and just modify them ad-hoc or by creating child classes
- define multiple examples
- define multiple sample responses
- define and reuse parameter definitions
- make your controllers readable again
Getting started
- Add
johnylemon/laravel-apidocs
repository
composer require johnylemon/laravel-apidocs
-
Register
Johnylemon\Apidocs\Providers\ApidocsServiceProvider
provider if not registered automagically . -
Install package. This command will publish all required assets.
php artisan apidocs:install
- Enjoy!
Generating route documentation
This package ships with command for rapid route definition generation.
php artisan apidocs:endpoint SampleEndpoint
Brand new SampleEndpoint
class will be placed within app\Apidocs\Endpoints
directory.
Target directory may be changed within your apidocs
config file.
This class contains only one describe
method, where you have to use any of available methods that will describe your endpoint. Like that:
<?php namespace App\Apidocs\Endpoints; use Johnylemon\Apidocs\Endpoints\Endpoint; use Johnylemon\Apidocs\Facades\Param; class SampleEndpoint extends Endpoint { public function describe(): void { $this->title('List users') ->desc('Returns paginated list of users'); } }
As you can see we set title and description as endpoint definition. Every method returns endpoint instance so you can chain them.
Endpoint available methods
- uri
- method
- group
- deprecated
- title
- description
- desc
- query
- params
- body
- header
- headers
- example
- examples
- returns
uri
Set uri. Called under the hood during endpoint mounting
$this->uri('/users');
method
Set endpoint method. Called under the hood during endpoint mounting
$this->method('POST');
group
Add endpoint to specific group. Group have to be defined previously.
$this->group('some-group-slug');
deprecated
Will mark endpoint as deprecated.
$this->deprecated();
title
Set endpoint title
$this->title('Create user resource');
description
Set endpoint description
$this->description('This endpoint contains logic for creating user resources based on provided data');
desc
Alias for description
query
Defines endpoint query params. See: parameters
$this->query([ 'page' => Param::type('int') ])
params
Defines endpoint route params. See: parameters
$this->query([ 'page' => Param::type('int') ])
body
Defines endpoint body params. See: parameters
$this->query([ 'page' => Param::type('int') ])
header
Defines endpoint header
$this->header('x-johnylemon', 'apidocs')
headers
Defines multiple endpoint header at once
$this->headers([ 'x-johnylemon' => 'apidocs', 'x-laravel' => 'framework' ])
example
Defines endpoint example. Optionally you can define example title
$this->example([ 'name' => 'johnylemon', 'web' => 'https://johnylemon.dev', 'email' => 'hello@johnylemon.dev' ], 'Store user')
examples
Define multiple endpoint examples at once
$this->examples([ [ 'name' => 'johny', 'web' => 'https://johnylemon.dev', 'email' => 'hello@johnylemon.dev' ], [ 'name' => 'lemon', 'web' => 'https://johnylemon.dev', 'email' => 'hello@johnylemon.dev' ] ])
returns
Define sample return value with status code. OPtinally you may define response description.
$this->returns(201, [ 'name' => 'johny', 'web' => 'https://johnylemon.dev', 'email' => 'hello@johnylemon.dev' ], 'User created') ->returns(401, [ 'status' => 'unauthorized', ], 'Auth issue');
Additionally you can use methods like returns201
(or any other status code)
// calling this ... $this->returns201([ 'name' => 'johny', 'web' => 'https://johnylemon.dev', 'email' => 'hello@johnylemon.dev' ], 'User created'); // ... is equivalent of this... $this->returns(201, [ 'name' => 'johny', 'web' => 'https://johnylemon.dev', 'email' => 'hello@johnylemon.dev' ], 'User created')
Endpoint definition usage
Okay, you created your first endpoint definition. Now it's time to use it as some real route definition.
Lets assume you have following routes:
Route::get('api/users', [UsersController::class, 'index']);
If you want to use App\Apidocs\Endpoints\SampleEndpoint
class as definition for first of them you should simply do this:
use App\Apidocs\Endpoints\SampleEndpoint; Route::get('api/users', [UsersController::class, 'index'])->apidocs(SampleEndpoint::class);
and... yes, thats it!
The only thing you have to do now is to call php artisan apidocs:generate
command and visit /apidocs
route to see it in action!
⚠️ This package must clear route cache to generate apidocs properly. If you are using route caching in your production environment rememeber to call
artisan route:cache
afterartisan apidocs:generate
command
Because apidocs
method returns endpoint class, you can chain methods during route definition. For example, you may want to mark your route as deprecated:
use App\Apidocs\Endpoints\SampleEndpoint; Route::get('api/users', [UsersController::class, 'index'])->apidocs(SampleEndpoint::class)->deprecated();
And because deprecated
method returns endpoint as well, you are allowed to use other endpoint methods.
⚠️ After calling
apidocs
method you cannot use route-specific methods, like, say,name
method. Be sure to callapidocs
method after all framework route-specific methods are called.
Resource routes
Sometimes you would like to use resource
or apiResource
methods to create bunch of typical CRUD endpoints. To specify definitions for these endpoints you have to use their names:
Route::resource('posts', PostsController::class)->apidocs([ 'posts.index' => PostsIndexEndpoint::class, 'posts.store' => PostStoreEndpoint::class, ]);
As you can see, you may ommit endpoints you dont want to be documented.
Sometimes you may be using resources
or apiResources
methods to create bunch of CRUDs at once. Because Laravel does not provide any handy hook for that, routes defined that way (and any other named routes!) may be documented using apidocs
helper:
// // your resoures // Route::resources([ 'users' => UsersController::class, 'posts' => PostsController::class, ]); // // defining endpoints // apidocs([ 'posts.index' => PostsIndexEndpoint::class, 'posts.store' => PostStoreEndpoint::class, 'users.index' => UsersIndexEndpoint::class, 'users.store' => UserStoreEndpoint::class, 'users.destroy' => UserStoreEndpoint::class, ]);
As metioned earlier, you may ommit endpoints you don't want to be documented.
Parameters
Some routes contains route parameters, like {user}
segment.
Sometimes you also want to use required or optional query parameters.
Routes like POST
, PATCH
, PUT
almost always expects some payload.
You can define them using params, and pass them as array to query
, body
and params
method when describing endpoint.
Lets assume your index
route from previously presented routes expects optional page
parameter.
Your definition should now contain additional query
method call with array of possible parameters. After that your code will look like that:
<?php namespace App\Apidocs\Endpoints; use Johnylemon\Apidocs\Endpoints\Endpoint; use Johnylemon\Apidocs\Facades\Param; class SampleEndpoint extends Endpoint { public function describe(): void { $this->title('List users') ->desc('Returns paginated list of users') ->query([ Param::int('page')->example(1)->default(1)->optional() ]) } }
Note that we did not specify parameter name (page
) by array key. It is not necessary when you define parameter name within class. But of course you can define them in different way:
use Johnylemon\Apidocs\Facades\Param; $this->query([ // parameter name will be `page` Param::int('page')->example(1)->default(1)->optional() // same effect: 'page' => Param::int('page')->example(1)->default(1)->optional() // same effect: 'page' => Param::type('int')->example(1)->default(1)->optional() // this parameter will be named `page_number` 'page_number' => Param::int('page')->example(1)->default(1)->optional() ])
As you can see, when parameter name is defined in both, array key and param name, array key will take precedence allowing you to create reusable custom parameters.
Route parameters and request body parameters can be defined same way.
Available methods
type
Define parameter type
Param::type('int');
name
Define parameter name
Param::name('username');
description
Define parameter description
Param::description('Unique username');
desc
Alias of description
. See description
required
Mark parameter as required
Param::required();
optional
Mark parameter as optional
Param::optional();
possible
Set parameter possible values
Param::possible([10, 100, 1000]);
enum
Alias for possible
. See possible
Param::enum([10, 100, 1000]);
default
Set parameter default value.
Param::default(42);
example
Set parameter example.
Param::example(42);
eg
Alias for example
. See example
Param class makes use of magic __call
method and allow you to define parameter type and name at once by using one of these methods: string
, array
, boolean
, bool
, integer
or int
use Johnylemon\Apidocs\Facades\Param; $this->query([ Param::string('slug'), // `slug` property, that should have `string` type Param::int('id'), // id `property`, that should have `int` type Param::array('roles'), // `roles` property, that should have `array` type ])
Custom parameters
It is common case that you may use page
or some other param in different endpoint definitions. So it may be cumbersome to write something like that over and over again:
$this->query([ Param::int('page')->example(1)->default(1)->optional() ]);
To solve that problem you may define PageParam
, which you can reuse as many times as you want without repeated code:
use App\Apidocs\Params\PageParam; (...) $this->query([ PageParam::class ]);
Creating custom parameters
Custom parameters may be created by typing
php artisan apidocs:param PageParam
New param class may be defined within __construct
method:
<?php namespace App\Apidocs\Params; use Johnylemon\Apidocs\Params\Param; class PageParam extends Param { public function __construct() { $this->name('page')->type('int')->default(1)->eg(42)->optional(); } }
Groups
Apidocs endpoints will be groupped. If no group is specified, default non-groupped
group will be used.
You can define your own groups using Johnylemon\Apidocs\Facades\Apidocs
facade:
use Johnylemon\Apidocs\Facades\Apidocs; Apidocs::defineGroup('users', 'Users', 'Manage users'); Apidocs::defineGroup('tickets', 'Tickets'); // Last parameter is optional
Groups must be defined before route registering. Perfect place for that is the the very beginning of your routes file.
Commands
This package ships with some commands that will be used for common tasks:
Testing
You can run the tests with:
vendor/bin/phpunit
License
The MIT License (MIT) Please see LICENSE for details.
Contact
Visit me at https://johnylemon.dev
Next
- improve examples
- improve responses
- improve resources
- improve layout
Developed with ❤ by johnylemon.