👏

react×Laravel環境構築×Todoリスト(1)

2024/10/12に公開

react×Laravel環境構築忘れないために

やること

  1. プロジェクトのセットアップ
  2. データベース設定
  3. APIエンドポイントの作成
  4. Reactコンポーネントの作成
  5. 画面の表示

プロジェクトのセットアップ

vsCodeを開いてctrl+@キーでターミナルを開く
C:\workspace何に作成したので
cdコマンドを使って自分の今いる位置からworkspaceに飛ぶ

composer create-project --prefer-dist laravel/laravel todoList

todoListの部分がプロジェクト名になります
ファイル作成が完了したらReactとLaravel Breezeのインストールします

cd todoList
composer require laravel/breeze --dev
php artisan breeze:install react --typescript
npm install react-router-dom @mui/material @emotion/react @emotion/styled axios
npm install

これでプロジェクトのセットアップは完了です。

データベース関連設定

apacheとMySQLをスタートさせて
xampp開いてMySQLのadminを押すと専用Webに飛ぶので、sqlをタブを選択して
以下を入力して実行

CREATE DATABASE tododb CHARACTER SET utf8;

vsCodeに戻って.envファイルの編集をします

DB_CONNECTION=mysql
DB_HOST=127.0.0.1
DB_PORT=3306
DB_DATABASE=your_database_name(tododb)
DB_USERNAME=your_database_username
DB_PASSWORD=your_database_password

your_database_name・your_database_username・your_database_passwordは自分のを入れてください
ターミナルでマイグレーションファイルの作成
以下はモデルとマイグレーションファイル同時に作成するコマンド

php artisan make:model Todo -m

マイグレーションファイルを編集
database/migrations/xxxx_xx_xx_create_todos_table.phpを編集

public function up()
	{
	    Schema::create('todos', function (Blueprint $table) {
	        $table->id();
	        $table->string('content');
	        $table->timestamps();
	    });
}

保存して以下を実行

php artisan migrate

APIエンドポイントの作成

app/Models/Todo.phpを編集

<?php

namespace App\Models;

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

class Todo extends Model
{
    use HasFactory;
    // $fillable に定義されたフィールドは、ユーザーがリクエストを通してデータをモデルに一括で割り当てる際に、保存できる属性のリストです。
    // $fillable を使うことで、セキュリティの確保を目的としています。リクエストから予期しないデータが送信されても、許可されたフィールドのみをデータベースに保存できるように制限することで、マスアサインメント脆弱性を防ぎます。
    protected $fillable = ['content'];
}
``
ターミナルでコントローラの作成
```js
php artisan make:controller TodoController

app/Http/Controllers/TodoController.phpを編集

<?php

namespace App\Http\Controllers;

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

class TodoController extends Controller
{
    // 新しいTodoを追加するメソッド
    public function setTodo(Request $request)
    {
        // リクエストのバリデーション: 'content' フィールドが必須であり、文字列で、最大255文字までであることを検証
        $request->validate([
            'content' => 'required|string|max:255'
        ]);

        // Todoを作成してデータベースに保存
        $todo = Todo::create([
            'content' => $request->content
        ]);

        // 作成したTodoをJSON形式で返す
        return response()->json($todo, 201); // ステータスコード201で返す
    }

    // 全てのTodoを取得して返すメソッド
    public function index()
    {
        // Todoのすべてのレコードを取得して変数に格納
        $todos = Todo::all();

        // 取得したTodoのリストをJSON形式で返す
        return response()->json($todos);
    }
}

ルートの設定
routes/web.phpの内容を変更

<?php

use App\Http\Controllers\ProfileController;
use Illuminate\Foundation\Application;
use Illuminate\Support\Facades\Route;
use App\Http\Controllers\TodoController;


// Todo APIエンドポイント
Route::post('/api/todo', [TodoController::class, 'setTodo']);
Route::get('/api/todo', [TodoController::class, 'index']);


// フロントエンドのすべてのリクエストをキャッチするルート
Route::get('{any}', function () {
    return view('app');
})->where('any','.*');

Reactコンポーネントの作成
登録用

import React, { useState } from "react";
import axios from "axios";
import { Box, Button, TextField, Typography } from "@mui/material";
import { useNavigate } from "react-router-dom";

const TodoForm: React.FC = () => {
    const [content, setContent] = useState("");
    const [message, setMessage] = useState("");
    const navigate = useNavigate();

    const handleSubmit = async (e: React.FormEvent) => {
        e.preventDefault();
        try {
            const response = await axios.post("/api/todo", { content });
            setMessage("Item added successfully");
            setContent("");
            navigate("/show");
        } catch (error) {
            setMessage("Failed to add item");
        }
    };
    return (
        <Box sx={{ maxWidth: 400, mx: "auto", mt: 4 }}>
            <Typography variant="h4" component="h1" gutterBottom>
                lifiGoalTodo
            </Typography>
            <form onSubmit={handleSubmit}>
                <TextField
                    label="Name"
                    value={content}
                    onChange={(e) => setContent(e.target.value)}
                    fullWidth
                    margin="normal"
                    required
                />
                <Button
                    type="submit"
                    variant="contained"
                    color="primary"
                    fullWidth
                >
                    目標を送信
                </Button>
            </form>
            {message && (
                <Typography variant="body2" color="textSecondary">
                    {message}
                </Typography>
            )}
        </Box>
    );
};

export default TodoForm;

表示用

import React, { useEffect, useState } from "react";
import axios from "axios";
import { Box, List, ListItem, ListItemText, Typography } from "@mui/material";
const ShowTodoList: React.FC = () => {
    const [todos, setTodos] = useState<{ id: number; content: string }[]>([]);
    const [loading, setLoading] = useState(true);
    useEffect(() => {
        const fetchTodos = async () => {
            try {
                const response = await axios.get("/api/todo");
                console.log(response.data);
                setTodos(response.data);
            } catch (error) {
                console.error("Error fetching items:", error);
            } finally {
                setLoading(false);
            }
        };
        fetchTodos();
    }, []);
    if (loading) {
        return <Typography>Loading...</Typography>;
    }
    return (
        <Box sx={{ maxWidth: 400, mx: "auto", mt: 4 }}>
            <Typography variant="h4" component="h1" gutterBottom>
                Item List
            </Typography>
            <List>
                {todos.map((todo) => (
                    <ListItem key={todo.id}>
                        <ListItemText primary={todo.content} />
                    </ListItem>
                ))}
            </List>
        </Box>
    );
};

export default ShowTodoList;

resources/js/app.tsxを編集

import "../css/app.css";

import React from "react";
import TodoForm from "./Components/TodoForm";
import ShowTodoList from "./Components/ShowTodoList";
import { createRoot } from "react-dom/client";
import { BrowserRouter as Router, Routes, Route } from "react-router-dom";

const App: React.FC = () => {
    return (
        <Router>
            <div>
                <Routes>
                    <Route path="/" element={<TodoForm />} />
                    <Route path="/show" element={<ShowTodoList />} />
                </Routes>
            </div>
        </Router>
    );
};

const container = document.getElementById("app");
if (container) {
    const root = createRoot(container);
    root.render(<App />);
}

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

        <title>{{ config('app.name', 'Laravel') }}</title>

        <!-- Fonts -->
        <link rel="preconnect" href="https://fonts.bunny.net">
        <link href="https://fonts.bunny.net/css?family=figtree:400,500,600&display=swap" rel="stylesheet" />

        <!-- Scripts -->
        @routes
        @viteReactRefresh
        @vite('resources/js/app.tsx')
    </head>
    <body class="font-sans antialiased">
        <div id="app"></div>
    </body>
</html>

XAMPPの設定
xamppを開いてApacheのConfigボタンを押してhttpd.configを選択して
ctrl+fでDocumentRootを検索して以下のように変更

DocumentRoot "C:\workspace\laravel-react\public"
<Directory "C:\workspace\laravel-react\public">

これはxamppの方で開くための設定(必要に応じて)
後はターミナルで

npm run dev

を打ってターミナルの方の
APP_URL:http://localhostをctrl+クリックで起動できる
ありがとうございました。

Discussion