Laravelで REST API を実装し Reactと連携したCRUDアプリ作成

業務でReact×Laravelのアプリ作成に関わっているので勉強しています。
今回は学習用のアウトプットです。

# Laravel側

## 環境構築

主にReactとLaravelの連携部分のアウトプットなので環境構築について詳細の説明は省きます。
今回はこちらを記事を参考にさせていただきました。

https://qiita.com/ucan-lab/items/d7752b43a9e89cc16b22

## API作成
手順はこちらの記事を参考にさせていただきました。

https://noumenon-th.net/programming/2020/02/12/laravel-api/

### モデルとマイグレーションの作成

```
$ php artisan make:model Book --migration
```

作成されたBookモデルに追記

```php:app/Models/Book.php 

<?php

namespace App\Models;

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

class Book extends Model
{
    use HasFactory;

    protected $fillable = [
        'title',
        'author',
    ];
}
```

マイグレーションファイル記述

```php:database/migrations/xxxx_xx_xx_xxxxxx_create_books_table.php
<?php

use Illuminate\Database\Migrations\Migration;
use Illuminate\Database\Schema\Blueprint;
use Illuminate\Support\Facades\Schema;

return new class extends Migration
{
    /**
     * Run the migrations.
     *
     * @return void
     */
    public function up()
    {
        Schema::create('books', function (Blueprint $table) {
            $table->id();
            $table->string('title');
            $table->string('author');
            $table->timestamps();
        });
    }

    /**
     * Reverse the migrations.
     *
     * @return void
     */
    public function down()
    {
        Schema::dropIfExists('books');
    }
};

```

マイグレーション実行

```php
$ php artisan migrate
```

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


```
$ php artisan make:controller BookController --api
```

### ルーティング設定

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

use App\Http\Controllers\BookController;
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 within a group which
| is assigned the "api" middleware group. Enjoy building your API!
|
*/

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

Route::apiResource('/books', BookController::class);
```

### ルーティング確認


```
php artisan route:list
```

結果

```php
  GET|HEAD        / .................................................................................................................................... 
  POST            _ignition/execute-solution ............................. ignition.executeSolution › Spatie\LaravelIgnition › ExecuteSolutionController
  GET|HEAD        _ignition/health-check ......................................... ignition.healthCheck › Spatie\LaravelIgnition › HealthCheckController
  POST            _ignition/update-config ...................................... ignition.updateConfig › Spatie\LaravelIgnition › UpdateConfigController
  GET|HEAD        api/books ......................................................................................... books.index › BookController@index
  POST            api/books ......................................................................................... books.store › BookController@store
  GET|HEAD        api/books/{book} .................................................................................... books.show › BookController@show
  PUT|PATCH       api/books/{book} ................................................................................ books.update › BookController@update
  DELETE          api/books/{book} .............................................................................. books.destroy › BookController@destroy
  GET|HEAD        api/user ............................................................................................................................. 
  GET|HEAD        sanctum/csrf-cookie ...................................................................... Laravel\Sanctum › CsrfCookieController@show

```

### コントローラーにメソッド追加

```php:app/Http/Controllers/BookController.php
<?php

namespace App\Http\Controllers;

use App\Models\Book;
use Illuminate\Http\Request;

class BookController extends Controller
{
    /**
     * Display a listing of the resource.
     *
     * @return \Illuminate\Http\Response
     */
    public function index()
    {
        $books = Book::all();
        return response()->json(
            $books, 200
        );
    }

    /**
     * Store a newly created resource in storage.
     *
     * @param  \Illuminate\Http\Request  $request
     * @return \Illuminate\Http\Response
     */
    public function store(Request $request)
    {
        $book = Book::create($request->all());
        return response()->json(
            $book, 201
        );
    }

    /**
     * Update the specified resource in storage.
     *
     * @param  \Illuminate\Http\Request  $request
     * @param  int  $id
     * @return \Illuminate\Http\Response
     */
    public function update(Request $request, $id)
    {
        $update = [
            'title' => $request->title,
            'author' => $request->author
        ];
        $book = Book::where('id', $id)->update($update);
        $books = Book::all();
        if ($book) {
            return response()->json(
                $books
            , 200);
        } else {
            return response()->json([
                'message' => 'Book not found',
            ], 404);
        }
    }

    /**
     * Remove the specified resource from storage.
     *
     * @param  int  $id
     * @return \Illuminate\Http\Response
     */
    public function destroy($id)
    {
        $book = Book::where('id', $id)->delete();
        if ($book) {
            return response()->json([
                'message' => 'Book deleted successfully',
            ], 200);
        } else {
            return response()->json([
                'message' => 'Book not found',
            ], 404);
        }
    }
}

```

フォームリクエストバリデーション作成

```
$ php artisan make:request StoreBook
```

```php:app/Http/Requests/StoreBook.php
<?php

namespace App\Http\Requests;

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

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

    /**
     * Get the validation rules that apply to the request.
     *
     * @return array
     */
    public function rules()
    {
        return [
            'title' => 'required',
            'author' => 'required',
        ];
    }

    public function messages()
    {
        return [
            'title.required' => 'タイトルが未入力です',
            'author.required' => '著者が未入力です',
        ];
    }

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

### curlコマンドでAPIを叩いてみる

新規作成

```
$ curl -X POST http://localhost:80/api/books  -d 'title=羅生門&author=芥川龍之介'
```

すべてのレコードを確認

```
$ curl http://localhost:80/api/books/
```


# React側

参考記事

https://qiita.com/sanogemaru/items/05c2e9381d6ba2d9fccf

https://reffect.co.jp/react/react-axios

## 新規プロジェクト作成

```
$ npx create-react-app {プロジェクト名} --template typescript
```

Appコンポーネントを以下の記述に変更

```react:
import axios from "axios";
import { useEffect, useState } from "react";
import "./App.css";

type Book = {
  id: number;
  title: string;
  author: string;
};

export const App = () => {
  const [books, setBooks] = useState<Book[]>([
    {
      id: 0,
      title: "",
      author: "",
    },
  ]);
  useEffect(() => {
    axios
      .get("http://localhost:80/api/books/")
      .then((response) => setBooks(response.data))
      .catch((error) => console.log(error));
  }, []);

  const [title, setTitle] = useState<string>("");
  const handleTitleChange = (e: React.ChangeEvent<HTMLInputElement>) => {
    setTitle(e.target.value);
  };

  const [author, setAuthor] = useState<string>("");
  const handleAuthorChange = (e: React.ChangeEvent<HTMLInputElement>) => {
    setAuthor(e.target.value);
  };

  const createNewBook = (): void => {
    axios
      .post("http://localhost:80/api/books/", {
        title: title,
        author: author,
      })
      .then((response) => {
        setBooks([...books, response.data]);
      })
      .then(() => {
        setAuthor("");
        setTitle("");
      })
      .catch((error) => {
        console.log(error);
      });
  };

  const deleteBook = (id: number) => {
    axios
      .delete(`http://localhost:80/api/books/${id}`)
      .then((response) => {
        console.log(response);
        setBooks(books.filter((book) => book.id !== id));
      })
      .catch((error) => console.log(error));
  };

  const modifyBook = (id: number) => {
    axios
      .patch(`http://localhost:80/api/books/${id}`, {
        title: title,
        author: author,
      })
      .then((response) => {
        setBooks(response.data);
      })
      .then(() => {
        setAuthor("");
        setTitle("");
      })
      .catch((error) => console.log(error));
  };

  return (
    <>
      <ul>
        {books.map((book) => (
          <>
            <li key={book.id}>
              タイトル:{book.title} 作者:{book.author}
              <button onClick={() => deleteBook(book.id)}>削除</button>
              <button onClick={() => modifyBook(book.id)}>更新</button>
            </li>
          </>
        ))}
      </ul>
      <label>
        タイトル:
        <input value={title} onChange={handleTitleChange} />
      </label>
      <label>
        作者:
        <input value={author} onChange={handleAuthorChange} />
      </label>
      <br />
      <button onClick={createNewBook}>作成</button>
    </>
  );
};


```

exportを変更したので他のファイルでエラーが発生した場合は適宜修正。

![movie.gif](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/1008745/8b73db8e-d7e0-9982-940d-c3fe0e6fadd7.gif)


以上になります。
型の定義方法等はまだまだ勉強中ですので修正点ありましたら教えていただきたく思います。