【Next.js + Laravel】野球の試合結果を記録するアプリケーションの開発 #2

## 概要
今回の記事ではバックエンド部分の開発について記していきたいと思います。

### 環境
Laravel(11.29.0)
MAMP

### プロジェクトの作成
Laravelプロジェクト立ち上げ
```
composer create-project --prefer-dist laravel/laravel baselog-backend
```
参考　--prefer-distとは

https://qiita.com/4U5zo/items/e28abef2df68d4a99ec8

データベース設定
.envファイルのデータベース接続設定を以下のように更新します。
```
DB_CONNECTION=mysql
DB_HOST=127.0.0.1
DB_PORT=3306
DB_DATABASE=baselog
DB_USERNAME=root
DB_PASSWORD=
```
Laravel Sanctumの導入
api.phpのインストール
```
php artisan install:api
```
参考　

https://qiita.com/fy-yuhcan/items/439137b1c149864448ed

参考　Laravel API Sanctumの仕組みについて

https://qiita.com/ucan-lab/items/3e7045e49658763a9566
今回はAPIトークン認証を使用したのですが、セキュリティの観点からSPA認証（クッキー認証）を利用した方が良い場合もあるようです。

Laravel Sanctumの設定
```php: User.php
class User extends Authenticatable
{
    use HasFactory, Notifiable, HasApiTokens;
```
HasApiTokensトレイをUserモデルに追加します。
参考

https://reffect.co.jp/laravel/laravel-sanctum-token

### モデルの作成
以下のコマンドでモデルとマイグレーションファイルを作成
```bash
php artisan make:model Game -m
```
まずは試合結果を保存するためのカラムを定義していきます。
```php: database/migrations/xxxx_xx_xx_create_games_table.php
public function up(): void
{
    Schema::create('games', function (Blueprint $table) {
        $table->id();
        $table->date('date');
        $table->string('home_team');
        $table->string('away_team');
        $table->timestamps();
    });
}
```
補足：voidはメソッドが何も値を返さないことを明示的に示すものであり、省略しても問題ありません。

マイグレーションを実行してテーブルを作成します。
```
php artisan migrate
```

### コントローラーの作成
続いて、試合結果を保存するためのコントローラを作成します。
```
php artisan make:controller GameController
```
GameController.phpに試合結果を保存するためのメソッドを定義します。
```php: GameController.php
public function store(Request $request)
{
    $request->validate([
            'date' => 'required|date',
            'home_team' => 'required|string',
            'away_team' => 'required|string',
            'home_total_score' => 'required|integer',
            'away_total_score' => 'required|integer',
            'home_total_hits' => 'required|integer',
            'away_total_hits' => 'required|integer',
            'home_total_errors' => 'required|integer',
            'away_total_errors' => 'required|integer',
        ]);

        for ($inning = 1; $inning <= 9; $inning++) {
            $request->validate([
                "home_inning_{$inning}" => 'required|integer',
                "away_inning_{$inning}" => 'required|integer',
            ]);
        }

        $game = Game::create($request->all());

        return response()->json(['message' => 'Game result saved collectlly!', 'data' => $game], 201);
}
```
まずリクエストのバリデーションを行います。
$request->all()でリクエストから送られてきたすべての入力データを配列として取得し、Game::createメソッドに渡しています。
そして、createメソッドを使用して、新しいGameレコードを作成し、データベースに保存します。
最後に、APIのレスポンスをJSON形式で返しています。
（createメソッドではなくsaveメソッドでもコントローラーのアクションを実装することができるが、saveメソッドを使う場合、各プロパティに手動でデータを割り当てる必要があるため冗長になってしまう。カラムが多い場合にはcreateメソッドの方が簡潔）

### APIルートの設定
routes/api.phpに試合結果の保存用エンドポイントを追加します。
```php: routes/api.php
use App\Http\Controllers\GameController;

Route::post('/games', [GameController::class, 'store']);
```
Route::postは、POSTリクエストのルートを定義するためのLaravelのメソッドです。
/gamesにPOSTリクエストが送られた時にこのルートがトリガーされます。GameController::classでGameControllerクラスを参照し、storeメソッドにルーティングしています。

### CORSの設定
CORSはブラウザが異なるオリジン間のリクエストを許可するかどうかを制御する仕組み。
```php: cors.php
'allowed_origins' => ['http//localhost:3000'],
```
すべてのオリジンを許可したい場合は*を使うことで許可することができます。
参考

https://zenn.dev/ritton/articles/e75172ed4b611f

### アクションの追加
新しい試合結果を保存するためのアクションしか準備していなかったので、試合結果の一覧の取得、特定の試合結果の取得、試合結果の更新、試合結果の削除この4つのアクションを追加してみたいと思います。
```php: GameController.php
public function index()
{
    $games = Game::all();

    return response()->json($games);
}

public function show($id)
{
    $game = Game::find($id);

    if (is_null($game)) {
            return response()->json(['message' => 'Not Found'], 404);
        }

    return response()->json($game);
}

public function update(Request $request, $id)
{
    $game = Game::find($id);

    if (is_null($game)) {
            return response()->json(['message' => 'Not Found'], 404);
        }

    $game->update($request->all());

    return response()->json($game);
}

public function destroy($id)
{
    $game = Game:find($id);

    if (is_null($game)) {
            return response()->json(['message' => 'Not Found'], 404);
        }

    $game->delete();

    return response()->json(null, 204);
```

### APIルートの追加
コントローラーのアクションの追加に伴ってルーティングを再度設定していきます。
```php: routes/api.php
Route::post('/games', [GameController::class, 'store']);//試合結果の追加
Route::get('/games', [GameController::class, 'index']);//試合結果の取得
Route::get('/games/{id}', [GameController::class, 'show']);//特定の試合結果の取得
Route::put('/games/{id}', [GameController::class, 'update']);//試合結果の更新
Route::delete('/games/{id}', [Gamecontroller::class, 'destroy']);//試合結果の削除
```
このようになりますが、実は上記のような基本的なCRUD操作はまとめて定義することができます。
```php: routes/api.php
Route::apiResource('games', GameController::class);
```
参考

https://zenn.dev/0bookbook/articles/11b168c2c228d7

APIテスト
POSTMANを利用して各エンドポイントが正しく動作するか確認しました。

## 参考文献

https://qiita.com/4U5zo/items/e28abef2df68d4a99ec8

https://qiita.com/fy-yuhcan/items/439137b1c149864448ed

https://qiita.com/ucan-lab/items/3e7045e49658763a9566

https://reffect.co.jp/laravel/laravel-sanctum-token

https://zenn.dev/ritton/articles/e75172ed4b611f

https://zenn.dev/0bookbook/articles/11b168c2c228d7