LaravelでToDoアプリのAPIを実装してみよう①

# 前書き
初学者向けにLaravelでAPIを作る方法を記事にさせていただきます。
今回はシンプルなToDoアプリのAPIを実装していきます。
記事ではLaravel10系を使用しています。

# Laravelの環境構築
今回は手軽に環境構築を行いたいのでSailを使用しようと思います。
Docker Desktopは事前にインストールしておいてください。
以下のコマンドを実行してアプリケーションの作成を行ってください。
```
curl -s "https://laravel.build/laravel-todo-api-sample" | bash
```

上手くいかない方は以下のQiitaの記事や公式ドキュメントをを参考にしみてください。

https://qiita.com/y_sone/items/2bf510e551cd14d22042

公式ドキュメント

https://readouble.com/laravel/10.x/ja/sail.html

https://readouble.com/laravel/10.x/ja/installation.html#docker-installation-using-sail

###  phpMyAdminを追加
DBをGUIで見たい人はphpMyAdminをymlファイルに追記してDockerを再起動してください。
**※必要がない人は飛ばしてください。**
```yml:docker-compose.yml
    selenium:
        image: selenium/standalone-chrome
        extra_hosts:
            - "host.docker.internal:host-gateway"
        volumes:
            - "/dev/shm:/dev/shm"
        networks:
            - sail
    # ----- ここから追記 -----
    phpmyadmin:
        image: phpmyadmin/phpmyadmin
        ports:
            - 8080:80
        environment:
            MYSQL_USERNAME: '${DB_USERNAME}'
            MYSQL_ROOT_PASSWORD: '${DB_PASSWORD}'
            PMA_HOST: mysql
        networks:
            - sail
    # ----- ここまで追記 -----
networks:
    sail:
        driver: bridge
volumes:
    sail-mysql:
        driver: local
    sail-redis:
        driver: local
    sail-meilisearch:
        driver: local
```
以下にのURLでphpMyAdminが表示されることを確認してください。
http://localhost:8080/

ログイン情報は.envを参考にしてください。
sailのデフォルトの設定では以下がログイン情報になっています。
|ユーザ名|パスワード|
| --- | ---|
|sail|password|


# テーブルの実装
今回はTODO用のテーブルを一つだけ用意して実装を行いたいと思います。

## テーブル設計

#### todosテーブル

| 論理名 | 物理名 | データ型 | 制約 |
| ---- | ---- | ---- | ---- |
| ID | id | UNSIGNED BIGINT | PRIMARY KEY, <br>AUTOINCREMENT |
| タイトル | title | VARCHAR(255) | NOT NULL |
| 詳細 | description | TEXT |  |
| 完了状態 | finished | BOOLEAN | NOT NULL |
| 作成日時 | created_at | TIMESTAMP | NOT NULL |
| 更新日時 | updated_at | TIMESTAMP | NOT NULL |

## マイグレーション

### マイグレーションファイル作成
以下のコマンドを実行してマイグレーションファイルを作成してください。
```
./vendor/bin/sail artisan make:migration create_todos_table
```

### テーブル定義
create_todos_table.phpを編集してToDoテーブルを定義してください。
```php:create_todos_table.php
public function up()
{
    Schema::create('todos', function (Blueprint $table) {
            $table->id();
            $table->string('title')->comment('タイトル');
            $table->text('description')->nullable()->comment('完了状態');
            $table->boolean('finished')->default(false)->comment('詳細');
            $table->timestamp('created_at')->useCurrent()->comment('作成日時');
            $table->timestamp('updated_at')->nullable()->comment('更新日時');
            $table->comment('ToDo');
    });
}
```

### マイグレーションの実行
以下のコマンドを実行してマイグレーションを実行してください。
```
./vendor/bin/sail artisan migrate
```

# モデルの実装

### モデルの作成
以下のコマンドを実行してマイグレーションを実行してください。
```
./vendor/bin/sail artisan make:model Todo
```

### モデルの作成
Todo.phpを編集して、ToDoアイテムに関するロジックを追加してください。
```php:Todo.php
class Todo extends Model
{
    protected $fillable = ['title', 'description', 'finished'];
}
```

# テストデータの作成
### Seederの作成
以下のコマンドを実行してSeederファイルを作成してください。
```
./vendor/bin/sail artisan make:seeder TodosTableSeeder
```

### Seederファイルの編集
TodosTableSeeder.phpを編集してテストデータを作成するロジックを追加してください。
今回は10件のテストデータを作成します。

```php:TodosTableSeeder.php
<?php

namespace Database\Seeders;

use Illuminate\Database\Seeder;
use Illuminate\Support\Facades\DB;

class TodosTableSeeder extends Seeder
{
    public function run()
    {
        for ($i = 1; $i <= 10; $i++) {
            DB::table('todos')->insert([
                'title' => 'ToDo' . $i,
                'description' => 'これはToDo' . $i . 'の詳細です。',
                'finished' => (bool)random_int(0, 1),
                'created_at' => now(),
                'updated_at' => now(),
            ]);
        }
    }
}
```

### Seederの実行
以下のコマンドを実行してSeederファイル実行し、テストデータの作成をしてください。
```
./vendor/bin/sail artisan db:seed --class=TodosTableSeeder
```

phpMyAdminなどを使用してテストデータが作られていることを確認してください。
http://localhost:8080/index.php?route=/sql&db=laravel_todo_api_sample&table=todos&pos=0


# コントローラーの作成
以下のコマンドを実行してコントローラーを作成をしてください。
```
./vendor/bin/sail artisan make:controller TodoController
```

# ToDoの一覧機能の実装
CRUDのR（Read）に該当する機能です。

### コントローラーの編集 
一覧データを取得するメソッドをTodoControllerに書いていきましょう。

```php:TodoController
<?php

namespace App\Http\Controllers;

use Illuminate\Http\Request;
use Illuminate\Http\JsonResponse; // 追加
use App\Models\Todo; // 追加
use Symfony\Component\HttpFoundation\Response; // 追加

class TodoController extends Controller
{
// --- 追加 ここから ---
    /**
     * ToDo一覧
     * 
     * @return JsonResponse
     */
    public function index(): JsonResponse
    {
        $todos = Todo::get();
        return response()->json(
            [
                'code' => Response::HTTP_OK,
                'todos' => $todos
            ]
        );
    }
// --- 追加 ここまで ---
}
```

`Todo::get()`でToDoテーブルのデータを全て取得し、`response()->json($todos)`とすることで取得したデータをJson形式にしてレスポンスすることができます。

`:JsonResponse`の記述は必須ではないですが、型宣言をすることによりindexメソッドがJsonResponseクラスのインスタンスを返すことを示してくれるようになります。
型宣言は、コードの可読性と安全性を向上させてくれるので覚えておくと良いと思います。

https://www.php.net/manual/ja/language.types.declarations.php

### ルーティングの設定
api.phpを開いて編集してください。

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

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

/*
|--------------------------------------------------------------------------
| API Routes
|--------------------------------------------------------------------------
|
| Here is where you can register API routes for your application. These
| routes are loaded by the RouteServiceProvider and all of them will
| be assigned to the "api" middleware group. Make something great!
|
*/

Route::middleware('auth:sanctum')->get('/user', function (Request $request) {
    return $request->user();
});

```
今回のアプリでは認証を行わないのでデフォルトで書かれている`auth:sanctum`は削除してしまいます。

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

use Illuminate\Support\Facades\Route;
use App\Http\Controllers\TodoController;
/*
|--------------------------------------------------------------------------
| API Routes
|--------------------------------------------------------------------------
|
| Here is where you can register API routes for your application. These
| routes are loaded by the RouteServiceProvider and all of them will
| be assigned to the "api" middleware group. Make something great!
|
*/

Route::get('/todo', [TodoController::class, 'index']);

```
今回はデータの取得を行う処理なのでGETを使用します。

### 動作確認
Postman等を使用して動作確認を行ないます。

https://www.postman.com/

使い方がわからない方は以下の記事などを参考にしてください。

https://qiita.com/sabewe/items/a9aa46c19938fdc80187

http://localhost/api/todo
上記URLにGETのHTTPリクエストを行いデータが取得できるか確認を行なってください。
![スクリーンショット 2024-01-20 21.26.10.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/703464/58b7e283-6cf0-aa5b-88cc-12c56ac83cfd.png)

### おまけ
一覧画面に完了状態の絞り込み機能を実装してみよう。

```php:TodoController.php
    /**
     * ToDo一覧
     * 
     * @param Request $request
     * @return JsonResponse
     */
    public function index(Request $request): JsonResponse
    {
        $finished = $request->input('finished'); // クエリパラメータ 'finished' の値を取得

        $query = Todo::query();

        if ($finished === 'true') {
            $query->where('finished', true);
        } elseif ($finished === 'false') {
            $query->where('finished', false);
        }
        $todos = $query->get();

        return response()->json(
            [
                'code' => Response::HTTP_OK,
                'todos' => $todos
            ]
        );
    }
```


# ToDoの新規登録機能の実装
CRUDのC（Create）に該当する機能です。

### フォームリクエストの作成
以下のコマンドを実行してフォームリクエストを作成をしてください。
```
./vendor/bin/sail artisan make:request TodoCreateRequest
```

以下のようにフォームリクエストを編集してください。
```php:TodoCreateRequest
<?php

namespace App\Http\Requests;

use Illuminate\Contracts\Validation\Validator;
use Illuminate\Foundation\Http\FormRequest;
use Illuminate\Http\Exceptions\HttpResponseException;
use Symfony\Component\HttpFoundation\Response;

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

    /**
     * Get the validation rules that apply to the request.
     *
     * @return array<string, \Illuminate\Contracts\Validation\ValidationRule|array<mixed>|string>
     */
    public function rules(): array
    {
        return [
            'title' => 'required|string|max:255',
            'description' => 'nullable|string',
            'finished' => 'boolean',
        ];
    }

    /**
     * バリデーションが失敗した場合の処理
     *
     * @param Validator $validator
     * @return void
     *
     * @throws HttpResponseException
     */
    protected function failedValidation(Validator $validator): void
    {
        throw new HttpResponseException(response()->json([
            'code' => Response::HTTP_BAD_REQUEST,
            'error' => $validator->errors()
        ], Response::HTTP_BAD_REQUEST));
    }
}
```
authorizeメソッドの返り値をtrueに変更することに注意してください。
failedValidationメソッドはFormRequestを使ってバリデーションを行いたいけど、そのままだとJSON形式でレスポンスを返してくれないのでエラー発生後にJSONで返してくれるように実装しています。



### コントローラーの編集 
データを新規登録するメソッドをTodoControllerに書いていきましょう。


```php:TodoController

// Classの上に追加
use App\Http\Requests\TodoCreateRequest;
use Illuminate\Support\Facades\Log;
use Throwable;

    /**
     * ToDoを作成
     * 
     * @param TodoCreateRequest $request
     * @return JsonResponse
     */
    public function create(TodoCreateRequest $request): JsonResponse
    {
        try {
            $todo = Todo::create($request->all());

            return response()->json(
                [
                    'code' => Response::HTTP_OK,
                    'todo' => $todo
                ],
                Response::HTTP_OK
            );
        } catch (Throwable $e) {
            Log::error($e);

            return response()->json(
                [
                    'code' => Response::HTTP_INTERNAL_SERVER_ERROR,
                    'message' => 'Internal Server Error'
                ],
                Response::HTTP_INTERNAL_SERVER_ERROR
            );
        }
    }
```
成功事にはEloquentでcreate処理を書いてJSON形式でレスポンスをするだけですね。
処理が失敗した場合の処理も忘れないようにしておきましょう。

### ルーティングの設定
api.phpにクリエイト処理のルーティングを設定をしてください。
```php:api.php
<?php

use Illuminate\Support\Facades\Route;
use App\Http\Controllers\TodoController;
/*
|--------------------------------------------------------------------------
| API Routes
|--------------------------------------------------------------------------
|
| Here is where you can register API routes for your application. These
| routes are loaded by the RouteServiceProvider and all of them will
| be assigned to the "api" middleware group. Make something great!
|
*/

Route::prefix('todo')->group(function () {
    Route::get('/', [TodoController::class, 'index']);
    Route::post('/create', [TodoController::class, 'create']);
});
```
同じtodoの処理を使用するので、ルーティングをグループ化してしまいましょう。
今回はPOSTです。


### 動作確認

http://localhost/api/todo/create
上記URLにPOSTのHTTPリクエストを行いデータが作成できるか確認を行なってください。

![スクリーンショット 2024-01-20 23.06.12.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/703464/b3b6bb57-3b06-4dc9-7f4b-e5093aa97c60.png)

# 後半へ続く
続きの記事はこちらになります。
https://qiita.com/kent0129/items/14169ef55d8e0e6425fb