【簡単】LaravelでAPIの実装

# はじめに
次のタスクが、久しぶりにLaravel+APIをさわるので復習を兼ねて備忘録として残します。

# 補足
- Postmanのインストール方法、説明は記載していないので、参考資料添付します![スクリーンショット 2024-02-05 21.54.02.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/923146/33abbe7e-9587-e21d-00d5-f8158a3be877.png)

- https://www.postman.com/downloads/
- [Postman 入門](https://zenn.dev/nameless_sn/articles/postman_tutorial)


# 開発環境
- Mac
- PHP：version 8.2.11
- Laravel：version 10.42.0
- Composer：version 2.2.4


# 実装

 ### プロジェクトを作成する
 - composerを使用してプロジェクトを作成する

 ```
 $ composer create-project laravel/laravel laravel-api-test
 ```

- サーバーを起動できるか確認

 ```
$ cd laravel-api-test
$ php artisan serve
 ```
- 下記の画面になっていればOK
![スクリーンショット 2024-01-27 18.07.41.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/923146/7336fad5-da78-8d29-8193-da6f4b7c2f5c.png)


 - Blog記事を作る想定で、マイグレーションのモデルを作成

```
$ php artisan make:model Blog -m
```

- 下記のようにマイグレーションに失敗する場合

![スクリーンショット 2024-01-28 20.06.30.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/923146/dfb5fed8-da69-eb78-5d2b-9ec5157f9a3c.png)

- Laravelとmysqlの接続が上手くいっていないので、.envファイルの設定を見直す

```.env
DB_CONNECTION=mysql
DB_HOST=127.0.0.1
DB_PORT=3306
DB_DATABASE=laravel
DB_USERNAME=xxx
DB_PASSWORD=xxx
```

- 見直したあとにマイグレーションコマンド実行

```
$ php artisan migrate
```

- createできていることを確認

![スクリーンショット 2024-01-30 9.15.34.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/923146/5d8a23e4-690d-745b-5a30-80adf9a16190.png)

### コントローラーを作成

```
$ php artisan make:controller ApiBlogController --model=Blog
```

- 処理を追加

```ApiBlogController.php
<?php

namespace App\Http\Controllers;

use App\Http\Requests\StoreBlogRequest;
use App\Models\Blog;
use Illuminate\Http\Request;

class ApiBlogController extends Controller
{
    /**
     * Display a listing of the resource.
     */
    public function index()
    {
        $blogs = Blog::all();
        return response()->json([
            'status' => true,
            'blogs' => $blogs
        ], 200);
    }

    /**
     * Store a newly created resource in storage.
     */
    public function store(StoreBlogRequest $request)
    {
        $blog = Blog::create($request->all());
        return response()->json([
            'status'=> true,
            'message'=>'Blog Created successfully!',
            'blog'=> $blog
        ], 201);
    }

    /**

     * Display the specified resource.
     */
    public function show($id)
    {
        $blogs = Blog::find($id);
        if($blogs){
            return response()->json([
                'message'=> 'Blog found',
                'data' => $blogs
            ], 200);
        } else {
            return response()->json([
                'message'=> 'Blog not found',
            ], 404);
        }

    }

    /**
     * Update the specified resource in storage.
     */
    public function update(Request $request, $id)
    {
        $update = [
            'title' => $request->title,
            'article' => $request->article
        ];
        $blog =  Blog::where('id', $id)->update($update);
        $blogs = Blog::all();
        if ($blog) {
            return response()->json([
                'message'=> 'Blog update',
                'data' => $blogs
            ], 200);
        } else {
            return response()->json([
                'message' => 'Blog not found',
            ], 404);
        }
    }

    /**
     * Remove the specified resource from storage.
     */
    public function destroy($id)
    {
        $blog = Blog::where('id', $id)->delete();
        if ($blog) {
            return response()->json([
                'message' => 'Blog deleted successfully',
            ], 200);
        } else {
            return response()->json([
                'message' => 'Blog not found',
            ], 404);
        }
    }
}


```

### バリデーション追加のため、StoreBlogRequest作成

```
$ php artisan make:request StoreBlogRequest
```

```StoreBlogRequest.php
<?php

namespace App\Http\Requests;

use Illuminate\Foundation\Http\FormRequest;
use Illuminate\Validation\ValidationException;
use Illuminate\Contracts\Validation\Validator;
use Illuminate\Http\Exceptions\HttpResponseException;

class StoreBlogRequest 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:20',
            'article' => 'required|string|max:255'
        ];
    }

    /**
     * Get error messages for defined validation rules
     *
     * @return array<string, string>
     */
    public function messages(): array
    {
        return [
            'title.required' => 'タイトルは必須です',
            'article.required' => '記事は必須です',
            'title.max' => 'タイトルは20文字以内です',
            'article.max' => '記事255文字以内です',
        ];
    }


    protected function failedValidation(Validator $validator)
    {
        $errors = (new ValidationException($validator))->errors();
        throw new HttpResponseException(response()->json([
            'message' => 'Failed validation',
            'errors' => $errors,
        ], 400, [], JSON_UNESCAPED_UNICODE));
    }
}

```

### route作成

```api.php
<?php

use Illuminate\Http\Request;
use Illuminate\Support\Facades\Route;
use app\Http\Controllers\ApiBlogController;

/*
|--------------------------------------------------------------------------
| 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();
});

Route::apiResource('blogs', ApiBlogController::class);

```

- 確認

```
$ php artisan route:list
```
![スクリーンショット 2024-02-08 22.15.27.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/923146/f535c949-3b47-a71b-a912-edf0d9b94168.png)



### 動作確認

- 下記でサーバー起動させた状態で、Postmanで動作確認する

```
$ php artisan serve
```

####  新規作成
- 下記の内容を作成する

| key | value |
|:-:|:-:|
| title  |  記事タイトル |
| article | 記事の内容 |


![スクリーンショット 2024-02-12 20.52.00.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/923146/5f199ca9-2c00-641c-7cc9-d8e6549cad92.png)

![スクリーンショット 2024-02-12 20.52.10.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/923146/8a75ec76-c816-6366-02de-159d9493e364.png)

#### 一覧を確認

- 下記のように入力し、GETで取得

![スクリーンショット 2024-02-12 21.03.11.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/923146/e5ade2d9-b497-dca8-a5da-5398cf5fdb66.png)


#### 特定のidを取得

![スクリーンショット 2024-02-12 20.51.29.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/923146/9f6b310e-f981-d49f-26d2-93627077bb08.png)


#### 特定のidの情報を更新

- PUTでidを指定し、内容更新されているのを確認

![スクリーンショット 2024-02-12 21.05.00.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/923146/74a7cc99-a489-af05-80f8-c43c960a82ef.png)

#### レコードの削除

- id2が削除されているのを確認

![スクリーンショット 2024-02-12 21.06.41.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/923146/e65b291f-97e5-058a-cb1c-46846fac7a91.png)

![スクリーンショット 2024-02-12 21.06.53.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/923146/1b538580-9904-9188-471e-a0da328b98cc.png)

## ステータスコードについて補足

| ステータスコード | 説明 |
|:-:|:-:|
| 200  | リクエストが成功したことを示す。 (操作の結果を表すリソースがメッセージ本文で送信される)  |
| 201  | リクエストは成功し、その結果新たなリソースが作成されたことを示す。|
| 204  | リクエストに対して送信するコンテンツはありませんが、ヘッダーは有用であることを示す。リクエスト成功して、返す値がない場合はこちらの方が適切|
| 400  | 構文が無効であるためサーバーがリクエストを理解できないことを示す。 |
| 404  | サーバーがリクエストされたリソースを発見できないことを示す。 通信先が有効であるものの、リソース自体が存在しないことを意味することがある。 |

## Eloquentリソースを使用してみる

- APIの実装で一覧と詳細のデータを返す時にこのリソースを使用すると楽みたいなので、実装して検証する。リソースクラスを作成

```
$ php artisan make:resource BlogResource
```

- BlogResourceへ追記

```BlogResource.php
<?php

namespace App\Http\Resources;

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

class BlogResource extends JsonResource
{
    /**
     * Transform the resource into an array.
     *
     * @return array<string, mixed>
     */
    public function toArray(Request $request): array
    {
        return [
            'id' => $this->id,
            'title' => $this->title,
            'article' => $this->article,
            'created_at' => $this->created_at,
            'updated_at' => $this->updated_at,
        ];    }
}

```

- コントローラー編集

```
<?php

namespace App\Http\Controllers;

use App\Http\Requests\StoreBlogRequest;
use App\Models\Blog;
use Illuminate\Http\Request;
use App\Http\Resources\BlogResource;

class ApiBlogController extends Controller
{
    /**
     * Display a listing of the resource.
     */
    public function index()
    {
    $blogs = Blog::all();
    return response()->json([
        'status' => true,
        'blogs' => BlogResource::collection($blogs)
    ], 200);
    }

(略)

    /**

     * Display the specified resource.
     */
    public function show($id)
    {
        $blog = Blog::find($id);
        if ($blog) {
            return response()->json([
                'status' => true,
                'blog' => new BlogResource($blog)
            ], 200);
        } else {
            return response()->json([
                'message' => 'Blog not found',
            ], 404);
        }
    }
    (略)
}


```

- 一覧取得

![スクリーンショット 2024-02-17 19.50.23.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/923146/6a750b3c-ecac-204c-01a6-d1de721fd714.png)

- 詳細取得

![スクリーンショット 2024-02-17 19.52.39.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/923146/6bee4f7c-36b4-f6a7-510e-df5a7d07283f.png)

どちらも取得できていること確認。Eloquentリソース使用したことなかったけど、使い勝手はよさそう。


# まとめ
Web APIの設計のベストプラクティスなど、色々考慮することもありますが、今回は簡単にLaravel+APIの実装を復習しました！今後テストコードやAPI認証の実装も追加していく予定です。

# 追記
- 2024/2/21 [【Laravel】APIテスト](https://qiita.com/miya1221/items/feec2e51f97ad48e8ef0)


# 参考
- [LaravelでAPIを作成しテストする方法](https://kinsta.com/jp/blog/laravel-api/)
- [Laravel 10.x Eloquent：APIリソース
](https://arc.net/l/quote/zkmisgec)
- [Laravel 10.x マイグレーション](https://arc.net/l/quote/dvgaiymm)
- [Laravel 10.x バリデーション](https://readouble.com/laravel/10.x/ja/validation.html)
- [HTTP レスポンスステータスコード](https://developer.mozilla.org/ja/docs/Web/HTTP/Status#%E6%83%85%E5%A0%B1%E3%83%AC%E3%82%B9%E3%83%9D%E3%83%B3%E3%82%B9)
- [HTTP 422エラーの解決方法](https://kinsta.com/jp/knowledgebase/http-422/)
- [Laravel 10.x Eloquent：APIリソース](https://readouble.com/laravel/10.x/ja/eloquent-resources.html)