LaravelとReactでSPAを開発してみよう
はじめに
はじめまして。私は2025年からエンジニアとしてキャリアを始めたものです。初めてのブログ投稿なので、温かい目で見守ってもらえると助かります。
今回は Laravel と React を使って、シンプルなTodoアプリを開発する方法をご紹介します。最初に、今回作成するアプリの画面を見ていきましょう。
実際の画面
トップページの画面
データ追加画面
データ追加後のトップページ
データ追加画面で入力した値(Todoテスト)が、一覧画面で表示されていることがわかります。
コードを書く前に
このアプリでは、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
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
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メソッドの中身を以下のように書いてください。
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ファイルを編集していきます。
<?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ファイルがあるので、そちらに以下の記述をしてください
<?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
<?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
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
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
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
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
と打っていただければ大丈夫です。