2
3

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

Laravel × Reactで作るTodoアプリ

Posted at

LaravelとReactでSPAを開発してみよう

はじめに

はじめまして。私は2025年からエンジニアとしてキャリアを始めたものです。初めてのブログ投稿なので、温かい目で見守ってもらえると助かります。

今回は LaravelReact を使って、シンプルなTodoアプリを開発する方法をご紹介します。最初に、今回作成するアプリの画面を見ていきましょう。

実際の画面

トップページの画面

top-page.png

データ追加画面

add-todo.png

データ追加後のトップページ

データ追加画面で入力した値(Todoテスト)が、一覧画面で表示されていることがわかります。
top-page-after-add.png

コードを書く前に

このアプリでは、LaravelとReactを用いてシンプルなTodoアプリを構築していきますが、初めての投稿になるので、コードを書く前にLaravelとReactについての概要を説明させていただきます。他の技術についても、適宜説明していきます。


Laravel

LaravelはPHPで書かれたモダンなWebアプリケーションフレームワークであり、MVC(Model-View-Controller)アーキテクチャに基づいています。

エレガントな構文と豊富な公式機能(ルーティング、認証、バリデーション、Eloquent ORMなど)により、迅速な開発と保守が可能です。

今回はLaravelを APIサーバー として用いて、ビュー側はReactで実装する SPA(Single Page Application) を開発していきます。


Reactの概要

Reactとは

ReactはFacebookが開発したJavaScriptライブラリで、ユーザーインターフェース(UI)構築を効率化するために設計されています。

主にSPA(シングルページアプリケーション)やモバイルアプリ開発で使われ、世界中の多くの企業や開発者に採用されています。

Reactの特徴

  • コンポーネントベースの開発
    UIを小さな部品(コンポーネント)に分割し、それぞれを独立して設計・管理できます。
    これによりコードの再利用性が高まり、大規模開発でも保守がしやすくなります。

  • 宣言的UI設計
    UIの状態をコードで宣言的に記述でき、UIの状態管理がシンプルになります。
    たとえば、「この状態のときはこう表示する」というロジックを明確に書けます。


環境構築の手順

今回は環境構築に Docker を用います。

Dockerとは、アプリケーションを動かすための環境を「ひとつの箱」にまとめて管理できるツールです。

通常、アプリを動かすには、OSの種類やソフトのバージョン、ライブラリのインストールなど、細かな準備が必要です。

しかしDockerを使えば、こうした設定をあらかじめ用意した コンテナ という「箱」にまとめることで、

誰のパソコンでも同じ環境で、同じようにアプリを動かせるようになります。


フォルダ構成としては以下のようになります。
laravel_todo (プロジェクトフォルダ)
├docker
| ├ nginx
|   └ default.conf
| └ php
|   └ Dockerfile
├ src (この中にLaravelのコードを配置)
└ docker-compose.yml

以上のファイルに下記の内容を書き加えてください。

docker-compose.yml

docker-compose.yml
version: '3.8'

services:
  app:
    build:
      context: .
      dockerfile: docker/php/Dockerfile
    container_name: laravel_app_todo
    working_dir: /var/www/html
    volumes:
      - ./src:/var/www/html
    ports:
      - 9000:9000
    depends_on:
      - db

  nginx:
    image: nginx:alpine
    container_name: laravel_nginx_todo
    ports:
      - 8080:80
    volumes:
      - ./src:/var/www/html
      - ./docker/nginx/default.conf:/etc/nginx/conf.d/default.conf
    depends_on:
      - app

  db:
    image: mysql:8.0
    container_name: laravel_db_todo
    environment:
      MYSQL_ROOT_PASSWORD: root
      MYSQL_DATABASE: laravel
      MYSQL_USER: laravel
      MYSQL_PASSWORD: secret
    ports:
      - 3306:3306

php/Dockerfile

FROM php:8.2-fpm

RUN apt-get update && apt-get install -y \
    zip unzip curl git libpng-dev libonig-dev libxml2-dev libzip-dev \
    && docker-php-ext-install pdo_mysql mbstring exif pcntl bcmath gd zip

COPY --from=composer:latest /usr/bin/composer /usr/bin/composer

nginx/default.conf

nginx/default.conf
server {
    listen 80;
    index index.php index.html;
    root /var/www/html/public;

    location / {
        try_files $uri $uri/ /index.php?$query_string;
    }

    location ~ \.php$ {
        fastcgi_pass app:9000;
        fastcgi_index index.php;
        include fastcgi_params;
        fastcgi_param SCRIPT_FILENAME $realpath_root$fastcgi_script_name;
        fastcgi_param DOCUMENT_ROOT $realpath_root;
    }
}

ここまで書いたら、プロジェクトフォルダ(私の場合はlaravel-todoフォルダ)で、以下のコマンドを実行してください。

docker compose up -d 

このコマンドによって、イメージをビルドしたあと、コンテナを起動したままの状態にします。
次に

docker-compose exec app composer create-project laravel/laravel . 

このコマンドを打ってもらえれば、srcフォルダにLaravelプロジェクトのファイルとフォルダが展開されます。

ここまでできれば、環境構築は終了です。

TodoアプリのAPIの実装

次はAPI部分の実装をしていきます。

早速、以下のコマンドを打ってください。「Todoモデル」と「そのテーブル定義(マイグレーション)ファイル」を同時に作成するLaravelのコマンドです。

docker-compose exec app php artisan make:model Todo -m  

上記のコマンドを打ってみると、migrationsフォルダに、
「20××_××_××_××××××_create_todos_table.php」このようなファイルが生成されるはずです。
そして、このファイルのupメソッドの中身を以下のように書いてください。

20××_××_××_××××××_create_todos_table.php
public function up(): void
{
    Schema::create('todos', function (Blueprint $table) {
        $table->id(); // 自動でIDを追加(主キー)
        $table->string('title'); // タイトル(文字列)カラム
        $table->timestamps(); // created_at, updated_atの2つのカラムを追加
    });
}

以上のコードを書くことで、todosという名前のテーブルがデータベースに作られ、
ID(自動採番される番号)、タイトル(title)、
そして作成日時(created_at)や更新日時(updated_at)といった
登録・更新のタイムスタンプ情報を保存できるようになります。

次に、マイグレーションを行います。マイグレーションとは、データベースのテーブル構造を管理・更新する仕組みのことで、
先ほど変更したマイグレーションファイルに記述した内容をもとに、実際にデータベース上にテーブルを作成・更新します。
以下がマイグレーションを実行するためのコマンドです

docker-compose exec app php artisan migrate

コントローラー部分

次に、コントローラーの方を書いていきましょう。

docker-compose exec app php artisan make:controller TodoController --api 

このコマンドによって、app/Http/ControllersフォルダにTodoController.phpファイルが生成されます。
そして、そのTodoController.phpファイルを編集していきます。

TodoController.php
<?php

namespace App\Http\Controllers;

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

class TodoController extends Controller
{
    /**
     * Todoの一覧を取得する処理
     */
    public function index()
    {
        // データベースに保存されているすべてのTodoを取得
        return Todo::all();
    }

    /**
     * 新しいTodoを登録する処理
     */
    public function store(Request $request)
    {
        // バリデーション:titleは必須で、文字列、最大255文字
        $request->validate([
            'title' => 'required|string|max:255',
        ]);

        // titleのみを取得してTodoとして保存
        $todo = Todo::create($request->only('title'));

        // 201(Created)でJSON形式で返却
        return response()->json($todo, 201);
    }

    /**
     * 指定されたIDのTodoを削除する処理
     */
    public function destroy($id)
    {
        // IDに該当するTodoを取得(存在しない場合はエラー)
        $todo = Todo::findOrFail($id);

        // Todoを削除
        $todo->delete();

        // 204(No Content)を返却(中身なし)
        return response()->json(null, 204);
    }
}

次に、app/ModelsフォルダにTodo.phpファイルがあるので、そちらに以下の記述をしてください

Todo.php
<?php

namespace App\Models;

use Illuminate\Database\Eloquent\Model;

class Todo extends Model
{
    /**
     * このモデルで「一括代入」できる属性を定義しています。
     *
     * たとえば、以下のようなコードで
     * Todo::create(['title' => '買い物に行く']);
     * のように title を一括で代入できるようにするには、
     * fillable にそのカラム名を指定する必要があります。
     */
    protected $fillable = [
        'title', // タイトルカラムのみ許可
    ];
}

protected $fillable: 配列に書かれたカラムだけが、create() や update() などで一括で代入できるようになります。

ルーティング

ここまで来たら、Laravel API のルーティングを設定して、フロントエンドなどから Todo データを操作できるようにしましょう。

まず、以下のコマンドを実行してください。

docker-compose exec app php artisan install:api

このコマンドを実行すると、routes フォルダ内に api.php というルーティングファイルが生成されます。
そして、作成された api.php ファイルを開いて、以下のコードを追加してください。

api.php

routes/api.php
<?php

use Illuminate\Support\Facades\Route;
use App\Http\Controllers\TodoController;

// Todo一覧を取得するAPI
Route::get('/todos', [TodoController::class, 'index']); // GET /api/todos

// 新しいTodoを作成するAPI
Route::post('/todos', [TodoController::class, 'store']); // POST /api/todos

// 指定したIDのTodoを削除するAPI
Route::delete('/todos/{id}', [TodoController::class, 'destroy']); // DELETE /api/todos/{id}

以上で、API部分が実装できました。

Todoアプリのフロントエンド部分の実装

今回はviteというものを使って環境構築していきます。こちらのツールを使うことで、開発サーバーが高速に起動できるなどのメリットがあります。
近年はreactやvueの環境構築を行う際には、viteを使うことが多くなっています。

npm create viteで新規プロジェクト作成

これからフロントエンドの方のフォルダを生成していきます。下記のコマンドを打つにあたって、
どのフォルダにいても書いませんが、プロジェクトフォルダ(私の場合で言うとlaravel-todoフォルダ)に移動するとわかりやすいと思います。
下記のコマンドをターミナルで打ってください

npm create vite 

そうすると、どのようなディレクトリ名にするか聞かれます。任意の名前にできますが、今回はfrontという名前にしてみます。
次にどのフレームワークを使うかを聞かれるので、今回はReactを選択してください。次に、

Typescript 

Typescript+swc 

Javascript 

Javascript+swc 

といった4つの選択肢が出ますが、今回はJavascriptを選んでください。
その後、下記の2つのコマンドを打ってください。

cd front 
npm install

ここまでくれば、Reactの方の準備が終わりました。
srcフォルダの中にあるindex.cssファイルとApp.cssファイルは今回使わないので、削除してください。
次に、srcフォルダの中に、AddTodo.jsxとTodoList.jsxを作成しておきます。

React側では、React Routerとaxiosというライブラリを使用します。
React RouterとはReact アプリケーションにルーティング(画面遷移)機能を追加するためのライブラリで、
axiosはHTTP通信(データの更新・取得)を簡単に行えるライブラリになります。
以下の2つのコマンドを実行することで追加できます。

npm install react-router-dom@6
npm install axios

src/App.jsでルーティングを設定します。

App.js

App.js
import { BrowserRouter as Router, Routes, Route, Link } from 'react-router-dom';
import "./styles.css"
import TodoList from './TodoList';
import AddTodo from './AddTodo';

function App() {
  return (
    <Router>
      <div style={{ padding: 20 }}>
        <h1>Todo App</h1>
        <nav>
          <Link to="/" style={{ marginRight: 10 }}>一覧</Link>
          <Link to="/add">追加</Link>
        </nav>
        <hr />
        <Routes>
          <Route path="/" element={<TodoList />} />
          <Route path="/add" element={<AddTodo />} />
        </Routes>
      </div>
    </Router>
  );
}

export default App;

次に、作成しておいたAddTodo.jsxファイルとTodoList.jsxファイルを編集しましょう

AddTodo.jsx

AddTodo.jsx
import React, { useState } from 'react';
import axios from 'axios';
import { useNavigate } from 'react-router-dom';

// APIのURL(Laravelで作成したTodo API)
const API_URL = 'http://localhost:8080/api/todos';

function AddTodo() {
  // 入力フォームのタイトルを管理するstate
  const [title, setTitle] = useState('');
  // エラーメッセージを管理するstate
  const [error, setError] = useState('');
  // ページ遷移を行うためのフック
  const navigate = useNavigate();

  // 新しいTodoを追加する非同期関数
  const addTodo = async () => {
    // 入力が空やスペースのみの場合はエラーを表示して処理を中断
    if (!title.trim()) {
      setError('タイトルを入力してください');
      return;
    }

    try {
      // APIにPOSTリクエストを送り、新しいTodoを追加
      await axios.post(API_URL, { title });
      // 追加成功したらトップページ(Todo一覧)に遷移
      navigate('/');
    } catch (err) {
      // エラーが起きた場合はコンソールに表示し、ユーザーにも通知
      console.error(err);
      setError('追加に失敗しました');
    }
  };

  return (
    <div className="container"> 
      <h2>Todo追加</h2>
      <input
        type="text"
        value={title} // 入力値をstateと同期
        onChange={e => setTitle(e.target.value)} // 入力が変わるたびにstate更新
        placeholder="タイトルを入力"
      />
      <button onClick={addTodo}>追加</button> {/* ボタンクリックでaddTodo実行 */}
      {/* エラーがあれば赤文字で表示 */}
      {error && <p style={{ color: 'red' }}>{error}</p>}
    </div>
  );
}

export default AddTodo;

TodoList.jsx

TodoList.jsx
import { useEffect, useState } from 'react';
import axios from 'axios';

// APIのエンドポイント(Laravelで定義したAPIのURL)
const API_URL = 'http://localhost:8080/api/todos';

function TodoList() {
  // useStateを使って、todoリストの状態(配列)を管理する
  const [todos, setTodos] = useState([]);

  // APIからtodoデータを取得する非同期関数
  const fetchTodos = async () => {
    const res = await axios.get(API_URL); // GETリクエストでデータを取得
    setTodos(res.data); // 取得したデータをtodosの状態にセット
  };

  // 指定したIDのtodoを削除する非同期関数
  const deleteTodo = async (id) => {
    await axios.delete(`${API_URL}/${id}`); // DELETEリクエストでサーバー側のデータを削除
    // 現在のtodos配列から、削除対象のIDを除外した新しい配列を作成
    setTodos(todos.filter(todo => todo.id !== id));
    /*
      filter() は配列の各要素に対して条件をチェックし、
      true の要素だけを新しい配列として返す。
      この場合、削除された todo の ID 以外のものだけを残す。
    */
  };

  // useEffectは、コンポーネントが最初に表示されたときに一度だけ実行される
  useEffect(() => {
    fetchTodos(); // 初回表示時にTodo一覧を取得
  }, []); // 空の配列を渡すことで「初回のみ」実行

  return (
    <div className="container">
      <h2>Todo一覧</h2>
      <ul>
        {todos.map(todo => ( // todosの中身を1件ずつ表示
          <li key={todo.id}>
            {todo.title}
            {/* 削除ボタンを押すと、deleteTodo関数が実行される */}
            <button onClick={() => deleteTodo(todo.id)} style={{ marginLeft: 10, backgroundColor: "red" }}>
              削除
            </button>
          </li>
        ))}
      </ul>
    </div>
  );
}

export default TodoList;

最後に、styles.cssファイルを以下のように書いて、見た目を整えましょう

styles.css

styles.css
body {
  font-family: 'Segoe UI', sans-serif;
  background-color: #f7f7f7;
  margin: 0;
  padding: 0;
}

.container {
  max-width: 600px;
  margin: 40px auto;
  background: white;
  padding: 20px;
  border-radius: 12px;
  box-shadow: 0 4px 12px rgba(0, 0, 0, 0.1);
}

nav {
  margin-bottom: 20px;
}

nav a {
  text-decoration: none;
  margin-right: 10px;
  color: #3498db;
  font-weight: bold;
}

nav a:hover {
  text-decoration: underline;
}

input[type="text"] {
  padding: 8px;
  width: 70%;
  border-radius: 6px;
  border: 1px solid #ccc;
  margin-right: 10px;
}

button {
  padding: 8px 14px;
  background-color: #3498db;
  border: none;
  border-radius: 6px;
  color: white;
  cursor: pointer;
}

button:hover {
  background-color: #2980b9;
}

ul {
  list-style: none;
  padding: 0;
}

li {
  padding: 10px 0;
  border-bottom: 1px solid #eee;
  display: flex;
  justify-content: space-between;
}

.error {
  color: red;
  margin-top: 8px;
}

お疲れ様でした。これでLaravelとReactでのSPAが完成しました。
この状態で、Laravelの方は、プロジェクトディレクトリ(私の場合はlaravel-todoフォルダ)で

docker compose up

と打っていただき、
Reactの方は、viteで環境構築をした際に作成したフォルダ(私の場合はfrontフォルダ)にいき、

npm run dev

と打っていただいたあと、
http://localhost:5173
にアクセスしていただけば、Todoアプリを使えます。
実際に動作した画像の方を改めて貼らせていただきます。

ちなみに、
Laravelのサーバーを停止したい場合は、プロジェクトディレクトリ(私の場合はlaravel-todoフォルダ)で

docker compose down

と打っていただけば、コンテナ自体を削除できます。
もう一度、サーバーを立ち上げたい場合は、プロジェクトディレクトリ(私の場合はlaravel-todoフォルダ)で

docker compose up

と打っていただければ大丈夫です。

実際の画面

トップページの画面

下記の画面が最初に表示される画面です
top-page.png

データ追加画面

追加画面を押せば、下記の画面に遷移します
add-todo.png

データ追加後のトップページ

データ追加画面で入力した値(Todoテスト)が、一覧画面で表示されていることがわかります。
top-page-after-add.png

2
3
0

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
2
3

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?