LaravelでのCRUD操作が行えるREST APIの作成

Laravelを使用して、CRUD(作成、読み込み、更新、削除)ができるREST APIを作成します。
内容について、詳しく説明を行っていませんが、とりあえず手を動かしてLaravelでREST APIを作ってみたいといった際に、参考になれば幸いです。

## 環境について
Laravel環境は、以下のDocker開発環境を使用させていただきます。
Docker環境の構築については、リンク先にてご確認ください。
<a href="https://qiita.com/ucan-lab/items/5fc1281cd8076c8ac9f4" target="_blank">最強のLaravel開発環境をDockerを使って構築する
</a>

## 接続テスト

実際にAPIが作動しているか確認するために、接続テスト用のエンドポイントを作成します。
ブラウザで、「<a href="127.0.0.1/api/test" target="_blank">127.0.0.1/api/test</a>」を開いた際に、「接続テスト成功！」が表示されると、接続テスト成功です。

```src/routes/api.php
<?php

use Illuminate\Http\Request;
use Illuminate\Support\Facades\Route;

// 接続できているか確認用
Route::get('/test', function () { return '接続テスト成功！'; });
```

![スクリーンショット 2022-09-25 7.57.03.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/483911/be289a8a-32ca-188f-a49e-73e3989ba1c3.png)

## マイグレーションファイルの作成

データベースのテーブル定義を作成します。
「birdテーブル」と「typeテーブル」を作成します。

 - birdテーブル
 
|カラム名|型|文字数制限|NULL許容|備考|
|:--|:--|:--|:--|:--|
|id|bigint| - | - | 自動生成されるID|
|created_at|timestanp| - | ○ | レコードがインサートされた時間 |
|updated_at|timestanp| - | ○ | レコードが更新された時間 |
|deleted_at|timestanp| - | ○ | レコードが削除された時間（論理削除) |
|name|varchar| 100文字 | - | 鳥の名前 |
|type|bigint| - | - | typeテーブルとのリレーション |

 - typeテーブル

|カラム名|型|文字数制限|NULL許容|備考|
|:--|:--|:--|:--|:--|
|id|bigint| - | - | 自動生成されるID|
|created_at|timestanp| - | ○ | レコードがインサートされた時間 |
|updated_at|timestanp| - | ○ | レコードが更新された時間 |
|deleted_at|timestanp| - | ○ | レコードが削除された時間（論理削除) |
|name|varchar| 100文字 | - | 鳥類 |

### ※ 環境についてで紹介したDocker環境の場合、コンテナ内のターミナルを操作する必要があります。

Dockerコンテナにターミナルをアタッチする

```bash
docker exec -it docker-laravel-app-1 sh
```

コンテナにアタッチしたことを確かめるため、Laravelのバージョンを確認する。

```bash
php artisan -v
```
Laravelのバージョンが表示されると、コンテナにアタッチできています。

![スクリーンショット 2022-09-25 11.57.09.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/483911/6cac95c4-f97b-f6c8-2173-c203e5a3d013.png)

**この後のコマンド操作はアタッチしたシェルで作業します。**

### bardテーブルの作成

```bash
php artisan make:migration create_bird_table
```

「src/database/migrations/」フォルダ内の「YYYY_MM_DD_hhmmss_create_bird_table.php」を編集します。
※ コマンドの実施時間によってファイル名が異なります。

```src/database/migrations/2022_09_01_000001_create_bird_table.php
    // 省略
    public function up()
    {
        Schema::create('bird', function (Blueprint $table) {
            $table->id();
            $table->timestamps();
            // --- 追加 ---
            // ソフトデリート
            $table->softDeletes()->comment('ソフトデリート');
            // 名前(100文字)
            $table->string('name', 100)->comment('名前');
            // 種別(typeテーブルとのリレーション)
            $table->bigInteger('type')->comment('種別');
            // -----------
        });
    }
    // 省略
```

### typeテーブルの作成

```bash
php artisan make:migration create_type_table
```

「src/database/migrations/」フォルダ内の「YYYY_MM_DD_hhmmss_create_type_table.php」を編集します。
※ コマンドの実施時間によってファイル名が異なります。

```src/database/migrations/2022_09_01_000002_create_type_table.php
    // 省略
    public function up()
    {
        Schema::create('type', function (Blueprint $table) {
            $table->id();
            $table->timestamps();
            // --- 追加 ---
            // ソフトデリート
            $table->softDeletes()->comment('ソフトデリート');
            // 名前(100文字)
            $table->string('name', 100)->comment('名前');
            // -----------
        });
    }
    // 省略
```

## シーダーファイルの作成

「birdテーブル」と「typeテーブル」のデータベースにインサートするデータを記述します。

### birdテーブルのシーダー作成

```bash
php artisan make:seeder BirdTableSeeder
```

「src/database/seeders/」フォルダ内の「BirdTableSeeder.php」を編集します。

```src/database/seeders/BirdTableSeeder.php
<?php

namespace Database\Seeders;

use Illuminate\Database\Console\Seeds\WithoutModelEvents;
use Illuminate\Database\Seeder;
// --- 追加 ---
use Illuminate\Support\Facades\DB;

class BirdTableSeeder extends Seeder
{
    public function run()
    {
        // --- 追加 ---
        // 日本時間に設定
        date_default_timezone_set('Asia/Tokyo');
        DB::table('bird')->insert([
            [
                'created_at' => date("Y/m/d H:i:s"),
                'updated_at' => date("Y/m/d H:i:s"),
                'name' => 'にわとり',
                'type' => 1,
            ], [
                'created_at' => date("Y/m/d H:i:s"),
                'updated_at' => date("Y/m/d H:i:s"),
                'name' => 'イワトビペンギン',
                'type' => 2,
            ], [
                'created_at' => date("Y/m/d H:i:s"),
                'updated_at' => date("Y/m/d H:i:s"),
                'name' => 'スズメ',
                'type' => 3,
            ]
        ]);
        // -----------
    }
}

```

### typeテーブルのシーダー作成

```bash
php artisan make:seeder TypeTableSeeder
```

「src/database/seeders/」フォルダ内の「TypeTableSeeder.php」を編集します。

```src/database/seeders/TypeTableSeeder.php
<?php

namespace Database\Seeders;

use Illuminate\Database\Console\Seeds\WithoutModelEvents;
use Illuminate\Database\Seeder;
// --- 追加 ---
use Illuminate\Support\Facades\DB;

class TypeTableSeeder extends Seeder
{
    public function run()
    {
        // --- 追加 ---
        // 日本時間に設定
        date_default_timezone_set('Asia/Tokyo');
        DB::table('type')->insert([
            [
                'created_at' => date("Y/m/d H:i:s"),
                'updated_at' => date("Y/m/d H:i:s"),
                'name' => 'キジ科',
            ], [
                'created_at' => date("Y/m/d H:i:s"),
                'updated_at' => date("Y/m/d H:i:s"),
                'name' => 'ペンギン科',
            ], [
                'created_at' => date("Y/m/d H:i:s"),
                'updated_at' => date("Y/m/d H:i:s"),
                'name' => 'スズメ科',
            ]
        ]);
        // -----------
    }
}
```

### シーダーファイルの読み込み設定

「src/database/seeders/」フォルダ内の「DatabaseSeeder.php」を編集します。

```src/database/seeders/DatabaseSeeder.php
<?php

namespace Database\Seeders;

// use Illuminate\Database\Console\Seeds\WithoutModelEvents;
use Illuminate\Database\Seeder;

class DatabaseSeeder extends Seeder
{
    public function run()
    {
        // --- 追加 ---
        $this->call([
            // 鳥テーブル
            BirdTableSeeder::class,
            // 分類テーブル
            TypeTableSeeder::class,
         ]);
         // -----------
    }
}

```

## マイグレーションとシーディングの実行

```bash
php artisan migrate --seed
```
データベースのマイグレーションとシーディングが正常に行われると、bardテーブルとtypeテーブルにデータが挿入されます。

![スクリーンショット 2022-09-25 17.34.18.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/483911/1699e9cc-8ff6-fe82-ea88-3a74162f2683.png)

## モデルの作成

birdテーブルのモデルとtypeテーブルのモデルを作成します。

### birdモデル

```bash
php artisan make:model Bird
```

「src/app/Models/」フォルダ内の「Bird.php」を編集します。

```src/app/Models/Bird.php
<?php

namespace App\Models;

use Illuminate\Database\Eloquent\Factories\HasFactory;
use Illuminate\Database\Eloquent\Model;
// --- 追加 ---
use Illuminate\Database\Eloquent\SoftDeletes;

class Bird extends Model
{
    use HasFactory;

    // --- 追加 ---
    use SoftDeletes;
    protected $table = 'bird';
    protected $guarded = array('id');
    // -----------
}
```

### typeモデル

```bash
php artisan make:model Type
```

「src/app/Models/」フォルダ内の「Type.php」を編集します。

```src/app/Models/Type.php
<?php

namespace App\Models;

use Illuminate\Database\Eloquent\Factories\HasFactory;
use Illuminate\Database\Eloquent\Model;
// --- 追加 ---
use Illuminate\Database\Eloquent\SoftDeletes;

class Type extends Model
{
    use HasFactory;

    // --- 追加 ---
    use SoftDeletes;
    protected $table = 'type';
    protected $guarded = array('id');
    // -----------
}
```

## 鳥の名前を取得するコントローラーの作成

```bash
php artisan make:controller BirdController --api
```

「src/app/Http/Controllers/」フォルダ内の「BirdController.php」を編集します。

```src/app/Http/Controllers/BirdController.php
<?php

namespace App\Http\Controllers;

use Illuminate\Http\Request;

// --- 追加 ---
use App\Models\Bird;
use App\Models\Type;
use Illuminate\Support\Facades\DB;

class BirdController extends Controller
{
    public function index()
    {
        // --- 追加 ---
        $result = Bird::select(
            // birdテーブルのid
            'bird.id as id',
            // birdテーブルの鳥の名前
            'bird.name as name',
            // typeテーブルの鳥の分類名
            'type.name as type'
        )->join('type', function ($type) {
            // bird.typeとtype.idとの紐付け
            $type->on('bird.type', '=', 'type.id');
        })->get();
        return $result;
        // -----------
    }
    // 省略
```

鳥の名前と種類を返すAPIを作成しました。

## 鳥の名前を取得するエンドポイントの定義

「src/routes/」フォルダ内の「api.php」を編集します。

```src/routes/api.php
// 省略
Route::prefix('bird')->group(function () {
    // 鳥の名前と種類の表示
    Route::get('/', 'App\Http\Controllers\BirdController@index');
});
// 省略
```

## 鳥の名前を取得するAPIのテスト

REST APIクライアントツール(<a href="https://www.postman.com/" target="_blank">postman</a>や、VS Code拡張の<a href="https://marketplace.visualstudio.com/items?itemName=rangav.vscode-thunder-client" target="_blank">Thunder Client</a>など。例では、Thunder Clientを使用しています。)で、「<a href="127.0.0.1/api/bird/" target="_blank">127.0.0.1/api/bird</a>」にGETリクエストを送ると、birdテーブルのid、鳥の名前、typeテーブルの分類名がJSON形式で表示されます。

※ APIにリクエストを送信した際に、サーバーエラーが発生する場合は、Laravelのキャッシュクリアを試してみてください。
参考：<a href="https://qiita.com/A-Kira/items/5fd039217ba992481267" target="_blank">Laravel 粘着質なcacheを削除しよう！</a>

![スクリーンショット 2022-09-25 19.52.14.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/483911/e7c966a6-ec03-8698-4b24-f1681ed4549b.png)

## 鳥の名前と種類名を登録するコントローラーの作成

「src/app/Http/Controllers/」フォルダ内の「BirdController.php」を編集します。

バリデーションとして、鳥の種類名と名前が存在しない場合は、登録せずに、エラーレスポンスを返すようにしています。

```src/app/Http/Controllers/BirdController.php
    // 省略
    public function store(Request $request)
    {
        // --- 追加 ---
        // 登録したい内容を受け取る
        $form = $request->all();

        // バリデーション
        // 鳥の種類名が存在するか
        if (!isset($form['type'])) {
            return response()->json(["鳥の種類名が存在しません"], 400);
        }
        // 鳥の名前が存在するか
        if (!isset($form['name'])) {
            return response()->json(["鳥の名前が存在しません"], 400);
        }

        // トランザクション
        DB::beginTransaction();
        // 日本時間に設定
        date_default_timezone_set('Asia/Tokyo');

        try {
            // 先に種類を登録
            $type = new Type();
            $type->fill([
                // 登録日時
                'created_at' => date("Y/m/d H:i:s"),
                // 更新日時
                'updated_at' => date("Y/m/d H:i:s"),
                // 種類名
                'name' => $form['type'],
            ])->save();

            // 鳥の種類を登録
            $bird = new Bird();
            $bird->fill([
                // 登録日時
                'created_at' => date("Y/m/d H:i:s"),
                // 更新日時
                'updated_at' => date("Y/m/d H:i:s"),
                // 鳥の名前
                'name' => $form['name'],
                // 鳥の種類
                'type' => $type->id,
            ])->save();

            // コミット
            DB::commit();
            return $bird;
        } catch (Exception $exception) {
            // 例外処理
            // ロールバック
            DB::rollBack();
            throw $exception;
        }
        // -----------
    }
    // 省略
```

## 鳥の名前と種類名を登録するエンドポイントの定義

「src/routes/」フォルダ内の「api.php」を編集します。

```src/routes/api.php
// 省略
Route::prefix('bird')->group(function () {
    // 鳥の名前と種類の表示
    Route::get('/', 'App\Http\Controllers\BirdController@index');
    // 鳥の名前と種類を登録
    Route::post('/', 'App\Http\Controllers\BirdController@store');
});
// 省略
```

## 鳥の名前と種類名を登録するAPIのテスト

REST APIクライアントツールで「<a href="127.0.0.1/api/bird/" target="_blank">127.0.0.1/api/bird</a>」にPOSTリクエストを送ると、鳥の名前と種類が登録できます。

 - リクエストボディ(JSON)

```JSON
{
  "type": "オウム科",
  "name": "オカメインコ"
}
```

![スクリーンショット 2022-09-25 20.22.21.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/483911/3c32820b-94ce-7bf7-3c46-ee9c9a5db795.png)

DBを見ると、POSTリクエストのボディの内容が登録されていることが確認できます。

![スクリーンショット 2022-09-25 20.24.13.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/483911/c0ac4ffc-da0d-fe52-b84a-fd764f213774.png)

## 鳥の名前と種類名を編集するコントローラーの作成

「src/app/Http/Controllers/」フォルダ内の「BirdController.php」を編集します。

バリデーションとして、birdテーブルに編集対象が存在するか、鳥の種類名と名前が存在しない場合は、登録せずに、エラーレスポンスを返すようにしています。

```src/app/Http/Controllers/BirdController.php
    // 省略
    public function update(Request $request, $id)
    {
        // --- 追加 ---
        // 登録したい内容を受け取る
        $form = $request->all();

        // バリデーション
        // 編集したいデータが存在するか
        if (!Bird::where('id', $id)->exists()) {
            return response()->json(["編集対象のレコードが存在しません"], 400);
        }
        // 鳥の種類名が存在するか
        if (!isset($form['type'])) {
            return response()->json(["鳥の種類名が存在しません"], 400);
        }
        // 鳥の名前が存在するか
        if (!isset($form['name'])) {
            return response()->json(["鳥の名前が存在しません"], 400);
        }

        // トランザクション
        DB::beginTransaction();
        // 日本時間に設定
        date_default_timezone_set('Asia/Tokyo');

        try {
            // birdテーブルのid検索
            $bird = Bird::find($id);
            // アップデート処理を行う
            $bird->fill([
                // 更新日時
                'updated_at' => date("Y/m/d H:i:s"),
                // 鳥の名前
                'name' => $form['name'],
            ])->update();

            // typeデーブルのid検索
            $type = Type::find($bird->type);
            // アップデート処理を行う
            $type->fill([
                // 更新日時
                'updated_at' => date("Y/m/d H:i:s"),
                // 鳥の種類
                'name' => $form['type'],
            ])->update();

            // コミット
            DB::commit();

            return response()->json([
                "bird" => $bird,
                "type" => $type
            ], 200);
        } catch (Exception $exception) {
            // 例外処理
            // ロールバック
            DB::rollBack();
            throw $exception;
        }
        // -----------
    }
    // 省略
```

## 鳥の名前と種類名を編集するエンドポイントの定義

「src/routes/」フォルダ内の「api.php」を編集します。

```src/routes/api.php
// 省略
Route::prefix('bird')->group(function () {
    // 鳥の名前と種類の表示
    Route::get('/', 'App\Http\Controllers\BirdController@index');
    // 鳥の名前と種類を登録
    Route::post('/', 'App\Http\Controllers\BirdController@store');
    // 鳥の名前と種類を更新
    Route::put('/update/{id}', 'App\Http\Controllers\BirdController@update');
});
// 省略
```

## 鳥の名前と種類名を編集するAPIのテスト

REST APIクライアントツールで「<a href="127.0.0.1/api/bird/update/2" target="_blank">127.0.0.1/api/bird/update/2</a>」にPUTリクエストを送ると、birdテーブルのidが2番(ペンギン科、イワトビペンギン)の鳥の名前と種類が編集できます。

 - リクエストボディ(JSON)

```JSON
{
  "type": "ペンギン目ペンギン科",
  "name": "キングペンギン"
}
```

![スクリーンショット 2022-09-25 20.49.51.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/483911/1f7e00a6-f804-8bc7-4b17-f4121a61527e.png)

DBを見ると、PUTリクエストのボディの内容に更新されていることが確認できます。

![スクリーンショット 2022-09-25 20.50.46.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/483911/03625a98-ca3b-87c2-4cd9-0dcc6ccfc0ec.png)

## 鳥の名前と種類名を論理削除するコントローラーの作成

「src/app/Http/Controllers/」フォルダ内の「BirdController.php」を編集します。

```src/app/Http/Controllers/BirdController.php
    // 省略
    public function destroy($id)
    {
        // --- 追加 ---
        // バリデーション
        // 削除対象のレコードが存在するか
        if (!Bird::where('id', $id)->exists()) {
            return response()->json(["削除対象のレコードが存在しません"], 400);
        }

        // トランザクション
        DB::beginTransaction();
        // 日本時間に設定
        date_default_timezone_set('Asia/Tokyo');

        try {
            // 削除対象のbirdレコードに紐づくtypeレコードのidを求める
            $TypeId = Bird::select('type')->where('id', $id)->first();
            // 削除したいbirdレコードを探し、削除
            Bird::find($id)->delete();
            // birdレコードに紐づくtypeレコードも削除
            Type::find($TypeId->type)->delete();
            // コミット
            DB::commit();
            return response()->json(['削除完了'], 200);
        } catch (Exception $exception) {
            // 例外処理
            // ロールバック
            DB::rollBack();
            throw $exception;
        }
        // -----------
    }
    // 省略
```

## 鳥の名前と種類名を論理削除するエンドポイントの定義

「src/routes/」フォルダ内の「api.php」を編集します。

```src/routes/api.php
// 省略
Route::prefix('bird')->group(function () {
    // 鳥の名前と種類の表示
    Route::get('/', 'App\Http\Controllers\BirdController@index');
    // 鳥の名前と種類を登録
    Route::post('/', 'App\Http\Controllers\BirdController@store');
    // 鳥の名前と種類を更新
    Route::put('/update/{id}', 'App\Http\Controllers\BirdController@update');
    // 鳥の名前と種類を論理削除
    Route::delete('/destroy/{id}', 'App\Http\Controllers\BirdController@destroy');
});
// 省略
```

## 鳥の名前と種類名を編集するAPIのテスト

REST APIクライアントツールで「<a href="127.0.0.1/api/bird/destroy/4" target="_blank">127.0.0.1/api/bird/destroy/4</a>」にDELETEリクエストを送ると、birdテーブルのidが4番(オウム科、オカメインコ)のレコードが論理削除されます。

![スクリーンショット 2022-09-25 21.13.58.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/483911/894acf94-206b-7139-c3b2-fda57dbbf14f.png)

DBを見ると、birdテーブルの「オカメインコ」とtypeテーブルの「オウム科」のレコードのdeleted_atに日付が入っていることが確認できます。

![スクリーンショット 2022-09-25 21.24.11.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/483911/04bd87f4-6924-797c-4f20-66347d892b2c.png)

---

以上で、Laraveで一通りのCRUD操作を行うREST APIができます。