🐙

API設計書を自動で作成できるLaravel Scribeのツールレビュー

2021/03/26に公開

今回は、API設計書を自動で作成できる Laravel Scribe のツールレビューをしてみたいと思います。

API開発を行う場合は、バックエンドとフロントエンドの架け橋となるAPI設計書が必要になってきます。(ないとコミュニケーションが大変そう)

でもExcelやスプレッドシートでAPI設計書を作ろうと思ってもなかなか見やすく書けなくないですか??良い書き方があったら教えてほしいです。(切実)

ところが、今回はAPI設計書を自動生成してくれる便利ツールがある!ということで、これは本当に実務で使えるレベルのものなのか!?ツールレビューしていきたいと思います。

公式のGitHubはこちら
公式のドキュメントはこちら

※残念ながら日本語はなさそうでした。

簡単なCRUDのAPIを作っておく

Laravel Scribeを試すために、下記のようなCRUDのAPIを作成しました。

一旦全て認証なしでふつうのCRUDのAPIを作ってみました。

①商品一覧を取得するAPI
エンドポイント:/api/item
メソッド:GET
認証なし
リクエストパラメータ: なし
レスポンスは画像の通り

②商品詳細を取得するAPI
エンドポイント:/api/item/{id}
メソッド:GET
認証なし
リクエストパラメータ: id: 商品ID
レスポンスは画像の通り

③商品を登録するAPI
エンドポイント:/api/item
メソッド:POST
認証なし
リクエストパラメータ: name: 商品名, price: 価格, description: 商品説明
レスポンスは画像の通り

④商品を更新するAPI
エンドポイント:/api/item/{id}
メソッド:PUT
認証なし
リクエストパラメータ: id: 商品ID, name: 商品名, price: 価格, description: 商品説明
レスポンスは画像の通り

⑤商品を削除するAPI
エンドポイント:/api/item/{id}
メソッド:DELETE
認証なし
リクエストパラメータ: id: 商品ID
レスポンスは画像の通り

Controllerはこのような感じです。コメントはあえて処理概要だけにしています。

<?php

namespace App\Http\Controllers\Api;

use App\Http\Controllers\Controller;
use App\Models\Item;
use Illuminate\Http\Request;

class ItemController extends Controller
{
    /**
     * 商品一覧を取得する
     */
    public function index()
    {
        $items = Item::all();
        return response($items);
    }

    /**
     * 商品を登録する
     */
    public function store(Request $request)
    {
        $itemAttribtues = $request->only(['name', 'price', 'description']);
        $item = Item::create($itemAttribtues);
        return response($item);
    }

    /**
     * 商品詳細を取得する
     */
    public function show($id)
    {
        $item = Item::find($id);
        return response($item);
    }

    /**
     * 商品を更新する
     */
    public function update(Request $request, $id)
    {
        $itemAttribtues = $request->only('name', 'price', 'description');
        Item::where('id', $id)->update($itemAttribtues);
        return response(['success' => true]);
    }

    /**
     * 商品を削除する
     */
    public function destroy($id)
    {
        Item::destroy($id);
        return response(['success' => true]);
    }
}

Laravel Scribeを使ってみた

Requirements

PHP 7.2.5 and Laravel/Lumen 6.0 or higher are required.

PHP7.2以上もしくはLaravel/Lumen 5.7以上が必要となります。
Laravel/Lumenを使用していない方も、Laravel/Lumenって何だ?って方もPHP7.2以上あれば問題ありません。

インストール方法

下記のコマンドでインストールできます

$ composer require --dev knuckleswtf/scribe 

設定ファイルを出力する

設定ファイルもコマンドで出力します

$ php artisan vendor:publish --provider="Knuckles\Scribe\ScribeServiceProvider" --tag=scribe-config
Copied File [/vendor/knuckleswtf/scribe/config/scribe.php] To [/config/scribe.php]
Publishing complete.

このコマンド実行後に config/scribe.phpが生成されています

実行してみる

いろいろ設定ができるみたいですが、とりあえずAPIドキュメントを出力してみます。

$ php artisan scribe:generate

そして、public/docs/index.html が生成されているので、http://127.0.0.1:8000/docs/index.htmlにアクセスします。

http://127.0.0.1:8000 のところは人によって違う可能性があるので、自分の設定に従ってください。

すると、こんな感じでドキュメントが出力されました。
おそらく左側に大量にある英語の部分は、Laravelで標準搭載されているログインビューなどなどがいろいろ出ているのかなと思います。

右側にはリクエストとレスポンスが記載されていますね!すごい!

コメントがないと全く意味をなさないドキュメントになります

商品更新のAPIをみると、リクエストパラメータで何を渡せばいいか分からず、どういうレスポンスが返ってくるかも分かりません。

このように、ドキュメントを見ても全く意味が分からないドキュメントになっていることが分かります。これだと実務で使えないですよね。

これは、APIのドキュメント自動生成時にコメントを解析しているため、コメントがないと情報が全くなくなります。

では、コメントをつけてみましょう!

コメントをきちんとつけると良いAPI設計書が出力されます!

Controllerの各メソッドにコメントをつけてみます。

今回は商品を更新するAPIで試してみます。
商品を更新するAPIの仕様をもう一度確認してみます。


④商品を更新するAPI
エンドポイント:/api/item/{id}
メソッド:PUT
認証なし
リクエストパラメータ: id: 商品ID, name: 商品名, price: 価格, description: 商品説明
レスポンスは画像の通り


では、この通りにコメントを書いていきます。

@param ... 関数の引数
@urlParam ... URLに含むパラメータ
@bodyParam ... リクエストボディに含むパラメータ
@response ... レスポンス例

    /**
     * 商品を更新する
     *
     * @param Request $request
     * @urlParam item integer required 商品ID
     * @bodyParam name string 商品名
     * @bodyParam price integer 価格
     * @bodyParam description string 商品説明
     * @response  {
     *  "success": true,
     * }
     */
    public function update(Request $request, int $id)
    {
        $itemAttribtues = $request->only('name', 'price', 'description');
        Item::where('id', $id)->update($itemAttribtues);
        return response(['success' => true]);
    }

もう一度ドキュメントを生成すると、非常に分かりやすくなっていることが分かります!

Tips: レスポンスはコメントで書かなくても自動生成できる

まずは設定を変更します。

デフォルトでは、GETリクエストのときのみレスポンスを自動生成してくれるようになっています。

config/scribe.php の'routes.apply.response_calls.methods'を見てください。

/*
 * If no @response or @transformer declarations are found for the route,
 * Scribe will try to get a sample response by attempting an API call.
 * Configure the settings for the API call here.
 */
'response_calls' => [
    /*
     * API calls will be made only for routes in this group matching these HTTP methods (GET, POST, etc).
     * List the methods here or use '*' to mean all methods. Leave empty to disable API calls.
     */
    'methods' => ['GET'],

こちらに、POSTやPUTやDELETEもいれていきます。これをすることでドキュメント生成時に一時的にPOSTメソッドを送ってDBをロールバックして...みたいなことをやってくれているみたいです。

/*
 * If no @response or @transformer declarations are found for the route,
 * Scribe will try to get a sample response by attempting an API call.
 * Configure the settings for the API call here.
 */
'response_calls' => [
    /*
     * API calls will be made only for routes in this group matching these HTTP methods (GET, POST, etc).
     * List the methods here or use '*' to mean all methods. Leave empty to disable API calls.
     */
    'methods' => ['GET', 'POST', 'PUT', 'DELETE'], // ← ここ!

そして、Controllerのコメントのパラメータ部分に例を示してあげます
パラメータ説明の最後に「Example: 〇〇」とつけると例として認識してくれます。

/**
 * 商品を更新する
 *
 * @param Request $request
 * @urlParam item integer required 商品ID Example: 1
 * @bodyParam name string 商品名 Example: 鉛筆
 * @bodyParam price integer 価格  Example: 100
 * @bodyParam description string 商品説明 Example: 使いやすい鉛筆です。
 */
public function update(Request $request, int $id)
{
    $itemAttribtues = $request->only('name', 'price', 'description');
    Item::where('id', $id)->update($itemAttribtues);
    return response(['success' => true]);
}

ドキュメントを再度生成すると、このようになりました!

レスポンスがsuccess => trueだけなので、分かりにくいかもしれません。

では、商品登録を見てみましょう
こちらも同様にコメントとリクエストパラメータの例を書いています


/**
 * 商品を登録する
 *
 * @param Request $request
 * @bodyParam name string 商品名 Example: 鉛筆
 * @bodyParam price integer 価格 Example: 100
 * @bodyParam description string 商品説明 Example: 使いやすい鉛筆です。
 */
public function store(Request $request)
{
    $itemAttribtues = $request->only('name', 'price', 'description');
    $item = Item::create($itemAttribtues);
    return response($item);
}

生成されたドキュメントはこちらです

すごい!分かりやすいですね!!

こういう場合はどうするの?

認証がついている場合

認証しているユーザーしかアクセスできないAPIにはこコメントに @authenticatedを追加します。

/**
 * 商品を更新する
 *
 * @authenticated ← 追加!
 * @param Request $request
 * @urlParam item integer required 商品ID Example: 1
 * @bodyParam name string 商品名 Example: 鉛筆
 * @bodyParam price integer 価格  Example: 100
 * @bodyParam description string 商品説明 Example: 使いやすい鉛筆です。
 */
public function update(Request $request, int $id)
{
    $itemAttribtues = $request->only('name', 'price', 'description');
    Item::where('id', $id)->update($itemAttribtues);
    return response(['success' => true]);
}

ドキュメントでは、 requires authentication という文字が出てきました!

バリデーションをしたい場合

API開発では、バリデーションルールもドキュメントに反映させたいですよね。

バリデーションをControllerに書いてみます

/**
 * 商品を更新する
 *
 * @authenticated
 * @param Request $request
 * @urlParam item integer required 商品ID Example: 1
 * @bodyParam name string 商品名 Example: 鉛筆
 * @bodyParam price integer 価格  Example: 100
 * @bodyParam description string 商品説明 Example: 使いやすい鉛筆です。
 */
public function update(Request $request, int $id)
{
    $request->validate([
        'name' => 'required|string|max:100',
        'price' => 'required|integer',
        'description' => 'required|string|min:50'
    ]);

    $itemAttribtues = $request->only('name', 'price', 'description');
    Item::where('id', $id)->update($itemAttribtues);
    return response(['success' => true]);
}

ドキュメントを生成して、確認してみると、バリデーションエラーの例もドキュメント化されています!

ちなみに、FormRequestを用いるとコメントの@bodyParamが必要なくなります。

FormRequestを作成し、このように記述します

<?php

namespace App\Http\Requests;

use Illuminate\Foundation\Http\FormRequest;

class ItemUpdateRequest extends FormRequest
{
    /**
     * Determine if the user is authorized to make this request.
     *
     * @return bool
     */
    public function authorize()
    {
        return true;
    }

    /**
     * Get the validation rules that apply to the request.
     *
     * @return array
     */
    public function rules()
    {
        return [
            'name' => 'required|string|max:100',
            'price' => 'required|integer',
            'description' => 'required|string|min:50'
        ];
    }


    public function bodyParameters()
    {
        return [
            'name' => [
                'description' => '商品名',
                'example' => '鉛筆'
            ],
            'price' => [
                'description' => '価格',
                'example' => 100,
            ],
            'description' => [
                'description' => '商品説明',
                'example' => '使いやすい鉛筆です。',
            ],
        ];
    }
}

そしてControllerに反映しつつ、 @bodyParamを削除します


/**
 * 商品を更新する
 *
 * @authenticated
 * @param Request $request
 * @urlParam item integer required 商品ID Example: 1
 */
public function update(ItemUpdateRequest $request, int $id)
{
    $itemAttribtues = $request->only('name', 'price', 'description');
    Item::where('id', $id)->update($itemAttribtues);
    return response(['success' => true]);
}

ドキュメントを生成すると、先程のバリデーションと同じドキュメントが出てきました

※なお、カスタムバリデーションはサポートしていないので、カスタムバリデーションについてはコメントに記述していくのが良いと思います。

APIドキュメントをグループ化したい

今回であれば、ItemのCRUDはItemグループという風にグループ化した方が見やすいです。

そういう場合はコメントに @group と記載していきます!
今回は Itemグループと名付けてみました(日本語打ったらドキュメントに反映されなかったので、英語が無難かもしれません)

/**
 * 商品を更新する
 *
 * @authenticated
 * @group Item
 * @param Request $request
 * @urlParam item integer required 商品ID Example: 1
 */
public function update(ItemUpdateRequest $request, int $id)
{
    $itemAttribtues = $request->only('name', 'price', 'description');
    Item::where('id', $id)->update($itemAttribtues);
    return response(['success' => true]);
}

@group Item をCRUDの全てにつけると
ドキュメントの左側がItemでグループ化されました!

APIの認証情報をドキュメントに含めたい時

APIで認証する場合に、ヘッダーにトークンを含めることがありますよね。

しかも認証が必要なAPIリクエストには全て共通であることが多いです。

その場合はScribeの設定ファイルに記述できます。

config/scribe.phpauth部分を見ていきます

    /*
     * How is your API authenticated? This information will be used in the displayed docs, generated examples and response calls.
     */
    'auth' => [
        /*
         * Set this to true if any endpoints in your API use authentication.
         */
        'enabled' => false,

        /*
         * Set this to true if your API should be authenticated by default. If so, you must also set `enabled` (above) to true.
         * You can then use @unauthenticated or @authenticated on individual endpoints to change their status from the default.
         */
        'default' => false,

        /*
         * Where is the auth value meant to be sent in a request?
         * Options: query, body, basic, bearer, header (for custom header)
         */
        'in' => 'bearer',

        /*
         * The name of the auth parameter (eg token, key, apiKey) or header (eg Authorization, Api-Key).
         */
        'name' => 'key',

        /*
         * The value of the parameter to be used by Scribe to authenticate response calls.
         * This will NOT be included in the generated documentation.
         * If this value is empty, Scribe will use a random value.
         */
        'use_value' => env('SCRIBE_AUTH_KEY'),

        /*
         * Placeholder your users will see for the auth parameter in the example requests.
         * Set this to null if you want Scribe to use a random value as placeholder instead.
         */
        'placeholder' => '{YOUR_AUTH_KEY}',

        /*
         * Any extra authentication-related info for your users. For instance, you can describe how to find or generate their auth credentials.
         * Markdown and HTML are supported.
         */
        'extra_info' => 'You can retrieve your token by visiting your dashboard and clicking <b>Generate API token</b>.',
    ],

APIで認証を伴うリクエストが発生する場合は enabled => trueにします。

ほとんどのAPIリクエストが認証が必要な場合は default => true にするとControllerのコメントに@authenticatedと入れる必要はありません。認証が不要な場合のみ@authenticatedと記載してください。

認証情報がどこに含まれているかを設定するのが in です。 ヘッダーの bearerにある場合は bearer と記載します。その他「query」「body」「basic」などが選べるみたいです。

認証情報のパラメータのキー名を nameで指定します。 先程bearerを指定した方は Authorization のケースが多いのかな?と思います。

認証情報の値は use_value で定義します。入力しなかった場合はランダム文字列が使われるそうです。

私は設定ファイルをこのように設定してみました。
見やすくするために標準で書かれてあるコメントを全て削除しました。

'auth' => [
    'enabled' => true,

    'default' => true,

    'in' => 'bearer',

    'name' => 'Authorization',

    'use_value' => env('SCRIBE_AUTH_KEY'),

    'placeholder' => '{YOUR_AUTH_KEY}',

    'extra_info' => 'You can retrieve your token by visiting your dashboard and clicking <b>Generate API token</b>.',
],

こちらでもう一度ドキュメントを生成してみます

requires authentication のラベルがついており、リクエスト例の部分に -H "Authorization: Bearer {YOUR_AUTH_KEY}" \ がついていますね!

めちゃくちゃ便利!!

総評

API開発時にはフロントエンドさんとバックエンドさんのコミュニケーションが書かせません。その場合にAPI設計書がきちんと書かれているとかなりコミュニケーションコストが減ります。

またExcelやスプレッドシートなど外部サービスを使用していると、更新漏れが発生したりします。

個人的に感じたLaravel Scribeを使用するメリットはこちらです

  1. コードでコメントを残しておくことで、コード修正時に変更しやすい(それでも変更するのを忘れる可能性もありますが)
  2. 自動で生成してくれるため設計書作成に時間があまりかからない
  3. 学習コストが低い
  4. 関数にコメントをつける癖ができるため、開発者にとても優しくなる。保守性アップ

デメリットはこちらです

  1. 全員が全員コメントの付け方を勉強しないといけない
  2. コメントがめっちゃ長くなることもある
  3. この場合コメントどう書くのおって迷う可能性があり、本来の開発に時間が充てられなくなる可能性がある(これはまだ実務で試してないため分かりません)

です!

個人サービス開発にはかなり便利ですが、まだなにか知らないデメリットが出てきそうなので判明次第追記していきます!

終わりに

今日はかなり便利なAPI設計書自動生成ツールを使ってみてレビューしてみました。

とりあえずやってみる精神で入れてみるのも全然ありだと思います。
適当にスプレッドシートで見づらい設計書を書くよりは、かなりましになると思いますし、メリットはかなり大きいと思います。

まだ試していない方はぜひやってみてくださいね!

Discussion