LoginSignup
82
52

More than 1 year has passed since last update.

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

Last updated at Posted at 2022-02-23

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

Laravel側

環境構築

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

API作成

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

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

$ php artisan make:model Book --migration

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

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',
    ];
}

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

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 artisan migrate

コントローラーの作成

$ php artisan make:controller BookController --api

ルーティング設定

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

結果

  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

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

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
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側

参考記事

新規プロジェクト作成

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

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

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

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

82
52
1

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
82
52