Skip to content

johnylemon/laravel-apidocs

Repository files navigation

Laravel API documentation generating tool

GitHub Workflow Status GitHub tag (latest by date)

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

img

Getting started

  1. Add johnylemon/laravel-apidocs repository
composer require johnylemon/laravel-apidocs
  1. Register Johnylemon\Apidocs\Providers\ApidocsServiceProvider provider if not registered automagically .

  2. Install package. This command will publish all required assets.

php artisan apidocs:install
  1. 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

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' => '[email protected]'
], 'Store user')

examples

Define multiple endpoint examples at once

$this->examples([
    [
        'name' => 'johny',
        'web' => 'https://johnylemon.dev',
        'email' => '[email protected]'
    ],
    [
        'name' => 'lemon',
        'web' => 'https://johnylemon.dev',
        'email' => '[email protected]'
    ]
])

returns

Define sample return value with status code. OPtinally you may define response description.

$this->returns(201, [
    'name' => 'johny',
    'web' => 'https://johnylemon.dev',
    'email' => '[email protected]'
], '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' => '[email protected]'
], 'User created');

// ... is equivalent of this...

$this->returns(201, [
    'name' => 'johny',
    'web' => 'https://johnylemon.dev',
    'email' => '[email protected]'
], '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 after artisan 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 call apidocs 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:

command description
apidocs:install install package
apidocs:endpoint {name} create endpoint class
apidocs:param {name} create param class

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.