LaravelをAPIサーバーとして利用してみる

LaravelをAPIサーバーとして、利用する際の大まかな手順をメモ。
Laravelのバージョンは`6系`です。

LaravelにReactを導入する手順は以下記事で書いているので、実際に試してみたい方は、参考にしてみて下さい。
[LaravelにReactを導入する時の手順](https://qiita.com/10mi8o/items/b435ef7fb838a40058c5)
## テストデータを用意する

### テーブル作成
まずテスト用のテーブルを作成します。

```terminal
php artisan make:migration create_books_table
```

```php:booksテーブル
    public function up()
    {
        Schema::create('books', function (Blueprint $table) {
            $table->increments('id');
            $table->string('title');
            $table->timestamps();
        });
    }
```

artisanコマンドを打って、テーブルを生成します。

```terminal
php artisan migrate
```

### Modelを作成
*Modelsディレクトリを作成してるので、自身の環境に読み替えてください。Modelsディレクトリを作成する場合は、以下記事を参照してみてください。
[[Laravel 6] Modelsディレクトリを作る](https://qiita.com/10mi8o/items/6329d3d4e2f5b6dd26d5)

```terminal
php artisan make:model Models/Book
```
### クラスを定義

```php:Bookクラスを定義
class Book extends Model
{
    protected $table = 'books';

    protected $guarded = array('id');

    public $timestamps = true;

    protected $fillable = [
        'title',
    ];
}
```
### ダミーデータを作成

ここでは、検証用にテストデータを作成しておきます。
方法はいくつかありますが、ここでは、Seederを使って、データを作成します。

```terminal:factoryクラスを作成
php artisan make:factory BookFactory
```

```php:生成した「BookFactory.php」に、生成するデータを定義
use App\Models\Book;
use Faker\Generator as Faker;

$factory->define(Book::class, function (Faker $faker) {
  return [
    'title' => $faker->realText(20, 1)
  ];
});
```

```terminal:Seederクラス作成
php artisan make:seeder "BooksTableSeeder"
```

```php:factoryヘルパ関数を利用してダミーデータを作成
class BooksTableSeeder extends Seeder
{
    /**
     * Run the database seeds.
     *
     * @return void
     */
    public function run()
    {
        factory(App\Models\Book::class, 10)->create();
    }
}
```

```terminal:seedコマンドを実行
php artisan db:seed --class=BooksTableSeeder
```
これでダミーデータが生成されます。


データは作れたので、ReactでAPI叩いて、情報を取得する処理を書いてみます。

## SPAのルーティングとコンポーネントの雛形作成
### SPAのルーティング

SPAでは、ルーティングをJS側で行うので、どのリクエストもSPAのトップにランディングするように設定しておきます。

以下記述を補足すると、`{any}`を設定することで、`/`以下へアクセスをすると、`app.blade.php`が表示される設定を行っています。


ただし、`{any}`だけでは、`/`へのアクセス時、404エラーが返ってくるため、ルートインスタンスの`where`メソッドを使用して、フォーマットを定義してます。

正規表現で、0文字以上の任意の文字列を設定しているので、これで、`/`にアクセスしても、`app.blade.php`が表示されることになります。

```php:routes/web.php
Route::get('/{any}', function(){
    return view('app');
})->where('any', '.*');//補足：.*は、正規表現で0文字以上の任意の文字列を意味する
```

### Reactコンポーネントを読み込む設定

以下のように記述します。
Laravel Mix は`app.js`の中身を見てコンパイルを行ってくれます。

*[Laravel Mix](https://laravel.com/docs/6.x/mix)は、フロントのアセットをコンパイル、バンドルしてくれるツールで、Webpackがベースになっています。


```php:resources/js/app.js
/**
 * Next, we will create a fresh React component instance and attach it to
 * the page. Then, you may begin adding components to this application
 * or customize the JavaScript scaffolding to fit your unique needs.
 */

require('./components/App');
```

### コンポーネントを作成する

`App.js`コンポーネントを作成します。

```react:components/App.js
import React from 'react';
import ReactDOM from 'react-dom';

const App = () => {
    return(
        <div>
            hoge
        </div>
    )
}

export default App;

if (document.getElementById('app')) {
    ReactDOM.render(<App />, document.getElementById('app'));
}
```

続いて、viewファイルを編集します。
`id="app"`が振られたdivタグにReactコンポーネントが表示されます。

```php:resources/views/app.blade.php
<!doctype html>
<html lang="{{ str_replace('_', '-', app()->getLocale()) }}">
<head>
    <meta charset="utf-8">
    <meta name="viewport" content="width=device-width, initial-scale=1">
    <meta name="csrf-token" content="{{ csrf_token() }}">
    <title>{{ config('app.name', 'React導入テスト') }}</title>
    <script src="{{ asset('js/app.js') }}" defer></script>
    <link rel="dns-prefetch" href="//fonts.gstatic.com">
    <link href="https://fonts.googleapis.com/css?family=Nunito" rel="stylesheet">
    <link href="{{ asset('css/app.css') }}" rel="stylesheet">
</head>
<body>
    <h3>BookList</h3>
    <div id="app"></div>
</body>
</html>
```

現時点で、ブラウザ表示しても、hogeしか表示されない状態です。
<img width="196" alt="スクリーンショット 2020-07-19 8.30.23.png" src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/33632/6a4b7340-e5e1-aaa1-c2c2-1989c43fe6f5.png">
## APIの作成とルーティング
### APIのルーティング

```php:routes/api.php
Route::group(['middleware' => 'api'], function() {
    Route::get('get', 'BookController@getBooks');
});
```

### コントローラーの追加

booksテーブルの全データを返します。

```terminal
php artisan make:controller BookController
```

```php:app/Http/Controller/BookController.php
class BookController extends Controller
{
    public function getBooks() {
        $books = Book::all();
        return response()->json($books, 200);
    }
}
```

## APIを叩いてデータを取得する

### 本のリストを取得するコンポーネントを作成

axiosを利用して、データを取得した後に、描画します。

```react:resources/js/components/BooksApp.js
import React, { useEffect, useState } from 'react';


const RenderRows = (props) => {
    return props.books.map(book => {
        return(
            <tr key={book.id}>
                <td>{book.id}</td>
                <td>{book.title}</td>
            </tr>
        )
    })
}

const BooksApp = () => {

    const [books, setBooks] = useState([]);

    useEffect(()=> {
        getBooksData()
    },[])

    function getBooksData() {
        axios.get('/api/get')
            .then(res => {
                setBooks(res.data);
                console.log(res.data);
            })
            .catch((error)=> {
                console.log(error)
            })
    }

    return(
        <React.Fragment>
            <table>
                <thead>
                <tr>
                    <th>ID</th>
                    <th>TITLE</th>
                </tr>
                </thead>
                <tbody>
                <RenderRows books={books}/>
                </tbody>
            </table>
        </React.Fragment>
    )
}

export default BooksApp;
```
### App.jsからBooksAppコンポーネントをインポートして表示させる。

```react:resources/js/components/App.js
import React from 'react';
import ReactDOM from 'react-dom';

import BooksApp from './BooksApp';

const App = () => {
    return(
        <div>
            <BooksApp />
        </div>
    )
}

export default App;

if (document.getElementById('app')) {
    ReactDOM.render(<App />, document.getElementById('app'));
}
```

これで表示が確認できたかと思います。

<img width="287" alt="スクリーンショット 2020-07-19 8.39.11.png" src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/33632/95aae151-8d97-83c0-2d51-fd0cb4ffc9fd.png">
以上、GETメソッドのみの簡単なものではありますが、手順の紹介でした。