Laravel の API Resource の使い方(基礎編)

目次

API Resources

LaravelをAPIサーバとして活用されている方は多いかと思います。その際、リレーションを適切に加えたり、ある程度返却のJSONデータを整えたりしたいと考えるかと思います。その際に有効なのが、この ResourceResourceCollectionと言うものになるかなと思います。今回はAPI Resourceの使い方について具体例を提示しながら解説をしていきたいと思います。

もしご自身で先に公式ドキュメントをご覧になられる方は下のリンクから眺めてみてください。
Eloquent: API Resources ー 公式ドキュメントです。英語なので、英語が苦手な方は下の翻訳されたものをご覧ください。
APIリソース ー 有志が翻訳してくれているものです。

対象となる方

  • Laravel学習中の方(初学者の方は少し難しいかもしれないですが、なるべく平易に書こうと思います)
  • JSONって何?という方
  • 返却データの整形どうしようかと悩まれている方

 ※Laravelのバージョンは8系で書いていきます。それ以前は確認しておりません。

JSONってなんだ?

まずもって、JSONってなんだ?というところを簡単に解説しておきたいと思います。
JSONとは「JavaScript Object Notation」の略語です。JavaScriptのオブジェクトのような表記です。利用に関してはJavaScriptに限定されたものではなく、様々なプログラミング言語で使うことができます。
表記のルールはいくつかあるのですが、大事なポイントとしては、オブジェクト(*注)であることと、そのオブジェクトのキーは文字列に限られているということでしょうか。
*注) ここで言うオブジェクトとは、順序がつけられていないキーとペアの集まりで、JSONでは連想配列と等価

例としては以下のようなものです。

{ "name": "Daisuke" }

データが複数ある場合はカンマでつなげるとOKです。
先ほど触れておりませんでしたが、キーは文字列である必要があるので、"(ダブルクオート)で囲む必要があります。

{ "id":  1, "name": "Daisuke", "age": null }

キー:バリュー のバリュー側については、上記のように文字列や数値、ヌル値だけでなく、true/false といった真偽値を利用することもできますし、バリューとしてオブジェクトや配列を利用することもできます。
例としては以下のようなものです。(少し見やすいように改行やインデントを整えておきます)
※データに意味はありません。適当です。

{
  "id": 1,
  "name": "Daisuke",
  "age": null,
  "membership": false,
  "relationships": {
    "favorite": {
      "food": ["オムライス", "バナナ"],
      "country": ["Japan", "USA"]
    },
    "keyword": ["Math", "Programing"] 
  }
}

近年は外部APIなどを使用した際に返却されるデータとしてJSONが活用されるケースが増えてきました。慣れが必要かと思いますが、そこまで難しい形式ではないと思いますので、一つひとつ整理しながらデータを読み取って見てください。

Laravel で JSON データを返すには

先ほどAPIの返却データとしては、JSON形式が使われるケースが増えてきたと触れましたが、では、実際に自作でJSONデータを返却するAPIサーバを作成したい場合はどうすればいいのでしょうか? APIサーバ自体の作り方詳細については、また別の機会で書くこととしますが、今回はその返却データの整形の部分に特化して述べていきたいと思います。(事実上APIサーバの構築をしていることにはなりますが、詳細な説明はしません。)
toJsonメソッドを使ってJSONへシリアル化することもできるのですが(一応ドキュメントを確認して見てください 日本語版 )、管理が煩雑になってしまったり、データのリレーションを表現する際に困ってしまうこともあります。
そこで、可読性を向上させ、データのリレーションや整形を容易にしてくれる、Resourceというものを利用してJSONデータを作ってみようと思います!

まず、今回作成するものの想定をしておきましょう!(全然複雑にせず、簡単なリレーションのみにします)
概要は以下のようなシンプルなものにしましょう!

  • 複数のユーザーが一言を投稿することができるアプリ
  • 画面上には全ユーザーの一言がリスト表示されている(表示項目:一言の内容、投稿時間、投稿ユーザー)

ログイン機能や画面の作成は一切この記事には書きませんのでご容赦ください。
一言を投稿するUserについてはLaravelインストール時に設置されているUserを活用することにします。

一言(Tweet) 関連 のファイル作成

では、早速作っていきましょう!(何度もくどいですが、Resourceの構築に関連する部分のみですのでご容赦を)
まずは、以下のコマンドを叩いて一言テーブル関連のファイルの作成をやりましょう!ちなみに、-aフラグを付けることによってモデルファイルだけでなくマイグレーションファイル、コントローラファイル、ファクトリファイル、シーダーファイルが同時に作成されます。

php artisan make:model Tweet -a

作成されたファイルの修正をやっていきましょう!
database/migrationsフォルダの中にある 〜create_tweets_table.php ファイルを開いてup()ファンクションの中を以下のように編集してください。

Schema::create('tweets', function (Blueprint $table) {
    $table->id();
    $table->string('tweet', 140);
    $table->foreignId('user_id')->constrained();
    $table->timestamps();
});

シンプルに一言を格納するカラムと誰が投稿したのかを特定するユーザIDを格納するカラムの生成をしただけです。
次にテーブル間のリレーションをモデルファイルに構築しましょう。リレーションについての詳しい説明は避けますが、今回の想定に関して補足しておくと、ユーザーテーブルとツイートテーブルの関係は1対多の関係になっています。考え方については公式ドキュメントを参照するか、別のサイト等で調べてみてください。
今回はUserモデルは既存のものを使います。app/Modelsフォルダの中にあるUser.phpファイルとTweet.phpファイルを編集してください。

<?php

namespace App\Models;

use Illuminate\Contracts\Auth\MustVerifyEmail;
use Illuminate\Database\Eloquent\Factories\HasFactory;
use Illuminate\Foundation\Auth\User as Authenticatable;
use Illuminate\Notifications\Notifiable;

class User extends Authenticatable
{
    use HasFactory, Notifiable;

    /**
     * The attributes that are mass assignable.
     *
     * @var array
     */
    protected $fillable = [
        'name',
        'email',
        'password',
    ];

    /**
     * The attributes that should be hidden for arrays.
     *
     * @var array
     */
    protected $hidden = [
        'password',
        'remember_token',
    ];

    /**
     * The attributes that should be cast to native types.
     *
     * @var array
     */
    protected $casts = [
        'email_verified_at' => 'datetime',
    ];

    /**
     * ユーザが投稿した全ての一言を取得
     */
    public function tweets()
    {
        return $this->hasMany(Tweet::class);
    }
}
<?php

namespace App\Models;

use Illuminate\Database\Eloquent\Factories\HasFactory;
use Illuminate\Database\Eloquent\Model;

class Tweet extends Model
{
    use HasFactory;

    /**
     * 一言を投稿したユーザを取得
     */
    public function user()
    {
        return $this->belongsTo(User::class);
    }
}

これで、このtweets()ファンクションやuser()ファンクションを呼び出せばモデルインスタンスに紐づいているデータを取得することができます。
一応、これで下準備は終わりました。次は本題の Resource の作成をやっていこうと思います。

Resource の作成

今回作成するリソースは、先ほども少し触れたのですが、JSONデータの整形を容易にすることができることと、リレーションを含めたい場合に大いに力を発揮します。では、早速作ってみましょう!以下のコマンドを叩いてください!

php artisan make:resource TweetResource

上記コマンドを叩くとapp/Http/ディレクトリの中にResourcesディレクトリが生成されて、その中に以下のようなTweetResource.phpファイルが生成されていると思います。

<?php

namespace App\Http\Resources;

use Illuminate\Http\Resources\Json\JsonResource;

class TweetResource extends JsonResource
{
    /**
     * Transform the resource into an array.
     *
     * @param  \Illuminate\Http\Request  $request
     * @return array
     */
    public function toArray($request)
    {
        return parent::toArray($request);
    }
}

この状態のままでも動くのですが、先ほどの趣旨を満たしません。updated_at といった今回Tweetを表示するのに必要のないカラムまで返却されてしまいますし、リレーションしているユーザーに関してはIDのまま返却されてしまいます。ということで、少し整形してみましょう!以下のように変更してください。

<?php

namespace App\Http\Resources;

use Carbon\Carbon;
use Illuminate\Http\Resources\Json\JsonResource;

class TweetResource extends JsonResource
{
    /**
     * Transform the resource into an array.
     *
     * @param  \Illuminate\Http\Request  $request
     * @return array
     */
    public function toArray($request)
    {
        $tweet_at = new Carbon($this->created_at);
        return [
            'data' => [
                'type' => 'tweet',
                'tweet_id' => $this->id,
                'attributes' => [
                    'tweet'    => $this->tweet,
                    'tweet_at' => $tweet_at->format('Y/m/d H:i'),
                    'tweet_by' => $this->user->name,
                ],
            ],
            'links' => [
                'self' => url('/tweet/'.$this->id),
            ]
        ];
    }
}

少し補足を入れておきます。このJSONのフォーマットは {json:api} というサイトを参考に整えました。フォーマットに関しては話せば長くなりますのでここでは触れるだけにしておきます。
今回必要なデータは「一言の内容、投稿日時、投稿ユーザー」の3つですので、そのデータだけを返却するようにしています。該当箇所は24行目から26行目です。26行目の $this->user->nameの部分がリレーションの部分になります。Tweetモデルの user()ファンクションを呼び出すことで名前の取得ができています。25行目はLaravelに標準搭載しているCarbonというライブラリを使って投稿日時の整形を行いました。

これで必要なデータのみ返却することができるようになりましたね!あとは、このリソースファイルの使い方です。
今回は routes/api.phpから TweetController を呼び出す形で作っていきますね。routesディレクトリ内にある api.php ファイルとapp/Http/Controllers ディレクトリ内にある TweetController.php ファイルを以下のように編集してください。

<?php

use Illuminate\Http\Request;
use Illuminate\Support\Facades\Route;
use App\Http\Controllers\TweetController;

Route::get('/tweet/{tweet_id}', [TweetController::class, 'show']);
<?php

namespace App\Http\Controllers;

use App\Models\Tweet;
use Illuminate\Http\Request;
use App\Http\Resources\TweetResource;

class TweetController extends Controller
{

    /**
     * 1件の一言を取得するFunction
     */
    public function show($tweet_id)
    {
        $tweet = Tweet::find($tweet_id);
        return new TweetResource($tweet);
    }

}

ポイントとしては、 7行目 でTweetResourceを読み込んで、18行目で Tweetモデルを引き渡しているところかと思います。
これでルーティングと呼び出しファンクションの構築が完成しました。データがDB上に存在すれば正しくデータが返却されると思いますので、確かめてみましょう!(データを挿入する部分は読者の皆様にお任せします…^^;)

/tweet/1 とか/tweet/3とかにアクセスすると以下のように1件のTweet情報が整形されて返却されているかと思います。

最後に、リソースコレクションについて触れておこうと思います。先程までの構築では、1件のTweetを整形することをやっておりました。ただ、全てのTweetを取得してリスト表示するということは必要な機能かと思います。そんな時に、いちいち1件ずつ送受信するということはほぼしないと思います。ではどうすればいいのか?
基本的にやり方は2つあると思います。

  1. TweetResource が継承しているJsonResourceクラスの collectionメソッドを使う
  2. ResourceCollectionクラスを継承した TweetCollection ファイルを生成する


何が違うのかというと、先程1件のJSONデータを整形した時のように、今回の複数データの場合もカスタマイズしたいかどうかです。①の方はカスタマイズできません。②の方はさらに情報を追加するといったカスタマイズができます。(例えば1件データの時のように "links"でアクセスURLを明示するなどです。)

今回は、①の collection メソッドを使う方で構築しますが、機会を見つけて ResourceCollection で構築する方も紹介できるようにします。

では、api.php と TweetController.php を再度編集していきましょう!

<?php

use Illuminate\Http\Request;
use Illuminate\Support\Facades\Route;
use App\Http\Controllers\TweetController;

Route::get('/tweet', [TweetController::class, 'index']);
Route::get('/tweet/{tweet_id}', [TweetController::class, 'show']);
<?php

namespace App\Http\Controllers;

use App\Models\Tweet;
use Illuminate\Http\Request;
use App\Http\Resources\TweetResource;

class TweetController extends Controller
{
    /**
     * 全ての一言を取得するFunction
     */
    public function index()
    {
        return TweetResource::collection(Tweet::all());
    }

    /**
     * 1件の一言を取得するFunction
     */
    public function show($tweet_id)
    {
        $tweet = Tweet::find($tweet_id);
        return new TweetResource($tweet);
    }
}

api.php に index() メソッドのルートを追記したことと、 TweetController.php に index() メソッドを追記しました。
index() メソッド内で TweetResource::collection(Tweet::all())と記述しているかと思います。これが、JsonResourceクラスの collection メソッドを呼び出している部分です。ここに Tweet::all()で全件取得したTweetを引き渡しています。実際の返却データは以下の画像のようになります。(画像は tweet_id = 3 で切れておりますが、存在する全てのツイートを整形したものが以下に列挙されていきます。)

JSONの構造としては "data" をキーとした配列([ ])の中に、JsonResourceのオブジェクトがずらっと並んでいる形になっています。完成しましたね^^v

まとめ

以上、今回は Laravel のAPIリソースの使い方でした。今後構築する際の参考になれば幸いです。今回はリレーションが一つだけでしたが、複数のリレーションや、もっとデータをカスタマイズしないといけない場合等あるかと思いますので、その際はぜひ、Resouceを使ってみてください!
私としては、ResourceCollection の使い方について書けていない部分がありますので、今後記事にしたいと思います。

よかったらシェアしてね!

この記事を書いた人

Web Developer / Educator