本文へ移動

こんにちは。ラーニング事業本部 事業推進1部のRYOTAです。

なぜSpring BootとReact学習を始めたか

私は普段、主にJavaやSpring Bootの研修講師として登壇しています。同時に、開発現場にも参画し、技術のトレンドを肌で感じています。研修と現場の双方で、React.jsによるSPA開発などフロントエンド技術の急速な進化と需要の高まりを痛感しています。

現在のWebアプリケーション開発では、フロントエンドはReactを中心に、バックエンドはSpring Bootをはじめ様々な技術が組み合わされる構成がよく見られます。このような組み合わせは、モダンな開発の代表的なパターンの一つと言えるでしょう。

研修でSpring Bootを教えていても、それをどうリッチなUIと繋げるのか。その「もう半分」の知識がなければ時代に取り残されるという危機感から、React学習を始めました。

本コラムでは、私のようなバックエンドの人間がReactを学び、Spring Bootと連携させたTODOアプリ開発の過程をお届けします。

React初心者を襲った最初の壁(環境構築とCORS)

開発環境について

今回の実装は、以下の環境で動作確認を行いました。

  • OS:Windows 11
  • Java:17
  • Spring Boot:Spring Boot 3.5.7 
  • Maven:4.0.0
  • Node.js:24.10.0
  • MySQL:8.0.34
  • IDE:IntelliJ IDEA / VS Code

開発環境構築とプロジェクト起動

まずは環境構築、と考えましたが、これが最初の壁でした。Spring BootとReact、それぞれ環境構築の方法が全く違います。

  • バックエンド (Spring Boot): IntelliJとMaven。雛形作成は「Spring Initializr」。
  • フロントエンド (React): VS CodeとNode.js。雛形作成は「Vite」。

フロントエンドのプロジェクト起動には、以下のコマンドを使いました。

# Reactプロジェクトの雛形作成(Viteを使用)
npm create vite@latest todo-ui
◇ Select a framework: 
│ React 
│
◇ Select a variant: 
│ JavaScript
│

# プロジェクトディレクトリへ移動
cd todo-ui

# 依存関係のインストールと開発サーバー起動
npm install
npm run dev

最終的にlocalhost:8080(Spring)、localhost:5173(React)、localhost:3306(MySQL)と、3つのサーバーが立ち並ぶことに。混乱しないように意識しました。

図1.各サーバーの関係性

最重要関門「CORSエラー」との戦い

環境構築が完了し、ブラウザからフロント・バックともにアクセスできたので、次にフロントとバックをつなげるサンプル作成を試みました。

Spring側でダミーデータを返却するAPIを作成し、ReactからそのAPIを叩いてデータを取得します。

結果はエラー。直接APIにアクセスすればデータが表示されますが、ReactからAPIを叩きに行くとエラーになります。エラー内容を確認すると「CORS error」と表示されていました。

図2.CORSエラー

「CORSエラー」とは、Reactアプリ(5173)からSpring BootのAPI(8080)へデータを取ろうとすると、ブラウザのセキュリティ機能が作動し、「ドメイン(ポート)が違うから危険だ!」と判断して通信をブロックしてしまうことです。

解決策: Spring BootでのCORS設定

Spring Boot側に「localhost:5173 からのアクセスは許可しますよ」と教える設定(WebMvcConfigurerなど)を追加することで解消することができました。

WebConfig.java

package com.example.todo;

import org.springframework.context.annotation.Configuration;
import org.springframework.web.servlet.config.annotation.CorsRegistry;
import org.springframework.web.servlet.config.annotation.WebMvcConfigurer;

@Configuration
public class WebConfig implements WebMvcConfigurer {

    @Override
    public void addCorsMappings(CorsRegistry registry) {
        registry.addMapping("/api/**") // APIエンドポイントのパスパターン
                .allowedOrigins("http://localhost:5173") // 許可するReactアプリのURL
                .allowedMethods("GET", "POST", "PUT", "DELETE", "OPTIONS") // 許可するHTTPメソッド
                .allowedHeaders("*") // 許可するヘッダー
                .allowCredentials(true); // Cookieなどを許可するか
    }
}

この設定によりCORSという最大の関門を突破し、ついにReactとSpring Bootが「繋がった」瞬間です。ここで初めて、モダンなWeb開発のスタートラインに立った気がしました。

図3.ReactとSpring Bootによるダミーデータの表示

TODOアプリの実装

さて、ここからが、本当のアプリケーション開発。早速、TODOアプリの基本機能であるCRUD(作成・読み取り・更新・削除)の実装に取り掛かりました。

APIを叩いて各機能を実装する

Spring Boot側のAPI実装は馴染み深いものでした。CRUD の4つのAPIを定義し、MySQL連携もSpring Data JPAでスムーズに実装できました。Spring Boot側のAPI実装は馴染み深かったため、ここではReact側、特にデータ取得部分のコードに焦点を当てます。

React側は、バックエンド開発とは全く異なる思考法が必要でしたが、Reactの核心「State(状態)」の理解で視界が開けました。

Reactでのデータ取得とStateの管理(src/App.jsx抜粋)

import React, { useState, useEffect } from 'react';

// APIのベースURLを定義
const API_BASE_URL = 'http://localhost:8080/api/todos';

function App() {
  // Stateの定義
  const [todos, setTodos] = useState([]);
  const [loading, setLoading] = useState(true);
  // ... (その他のStateの定義は省略) ...

  // データ取得(API通信)と画面への反映
  // 【R: Read】タスク一覧の取得
  useEffect(() => {
    const fetchTodos = async () => {
      try {
        const response = await fetch(API_BASE_URL);// APIにGETリクエスト
        if (!response.ok) {
          throw new Error('データの取得に失敗しました。');
        }
        // 取得したJSONデータをJavaScriptのオブジェクトに変換
        const data = await response.json();
        // 【Point】取得したデータをStateにセット
        setTodos(data);
      } catch (err) {
        setError(err.message);
      } finally {
        setLoading(false);
      }
    };

    fetchTodos();
  }, []); // 依存配列が空の [] なので、コンポーネント初回描画時のみ実行される。

  // ... (その他の関数の処理内容は省略) ...
  const handleAddTodo = async (e) => {
  // ... (省略) ...
  };

  const handleToggleComplete = async (id) => {
  // ... (省略) ...
  };

  const handleDeleteTodo = async (id) => {
  // ... (省略) ...
  };


  if (loading) return <div className="App"><h1>ローディング中...</h1></div>;
  if (error) return <div className="App"><h1 style={{ color: 'red' }}>エラー: {error}</h1></div>;

  // メインのTODOアプリの表示内容
  return (
    <div className="todo-container">
      {/* ... (入力フォームは省略) ... */}

      {/* Stateに保持されたデータをループして表示 */}
      {/* タスク一覧 */}
        <ul className="todo-list">
            {todos.map((todo) => (
            <li
                key={todo.id}
                className={`todo-item ${todo.completed ? 'completed' : ''}`}
            >
                <input
                    type="checkbox"
                    className="todo-checkbox"
                    checked={todo.completed}
                    onChange={() => handleToggleComplete(todo.id)}
                />
                <span className="todo-title">{todo.title}</span>
                <button
                    className="delete-btn"
                    onClick={() => handleDeleteTodo(todo.id)}
                >
                削除
                </button>
            </li>
            ))}
        </ul>
    </div>
  );
}

export default App;

APIで取得した一覧をuseStateで保持し、タスク追加・削除時も、APIを叩いた後、必ずこのtodosのStateを更新します。Stateが変更されると、ブラウザが自動で再描画されます。これこそがReactの「宣言的UI」でした。

タスク取得・追加・更新・削除を実装した画面が以下になります。

図4.APIの作成と利用

コンポーネント分割で構造を整理する

最後に、Reactの真髄である「コンポーネント分割」に挑戦しました。最初は一つの巨大なファイルになりがちでしたが、これを「タスク追加フォーム」「タスク一覧」「タスクアイテム」の3つに分離しました。

図5.コンポーネント構成

それぞれの役割(UIとロジック)が明確になることで、コードの見通しが劇的に改善されます。親から子へデータを渡す「props」の仕組みも実践できました。

Propsにより、App.jsxから受け取り、TodoList.jsxに渡すデータの受け渡し(TodoList.jsx抜粋)

import React from 'react';
import TodoItem from './TodoItem'; // 個別のアイテムコンポーネントをインポート

/**
 * App -> TodoList
 * 引数として以下のPropsを受け取る
 * @param {{ todos, onToggle, onDelete }} 
 */
export default function TodoList({ todos, onToggle, onDelete }) {
  return (
    <ul className="todo-list">
      {todos.map((todo) => (
     // TodoItemコンポーネントの呼び出し。TodoList -> TodoItem へPropsを渡す。
        <TodoItem
          key={todo.id} 
          todo={todo} // Props①
          onToggle={onToggle} // Props②
          onDelete={onDelete} // Props③
        />
      ))}
    </ul>
  );
}

このように「関心の分離」を徹底することで、Reactの設計思想の強力さを肌で感じました。

図6.コンポーネント構成改善とCSSの追加によって作成できたページ

簡易アプリを完成させて見えたこと

簡易的なTODOアプリとはいえ、Spring BootとReactを連携させた開発は、多くの「気付き」を与えてくれました。

バックエンドエンジニアの視点で最も強く感じたのは、両者の「役割(関心)の分離」がいかに明確か、ということです。 Spring Bootは「APIサーバー」として、データの整合性やロジックという「状態の管理」に集中します。一方、Reactは「UIクライアント」として、APIから受け取った「状態(State)」を、いかに効率よく宣言的にユーザーへ見せるかに集中します。

かつてのJSPやThymeleafのようにバックエンドが画面描画まで担当していた時代とは異なり、この疎結合なアーキテクチャこそが、モダンな開発の基盤となっているのです。

研修講師として、この「APIを作る本当の意味」と「フロントエンドと疎結合であることの価値」を体感できたことは、何より大きな収穫でした。

まとめ

今回の学習は、環境構築やCORSという壁にぶつかりながらも、無事にTODOアプリを完成させることができました。

最大の収穫は、Reactの技術以上に、Spring Boot(API)とReact(UI)が「関心を分離」するモダンな疎結合アーキテクチャの強力さを体感できたことです。

  • Spring Boot (APIサーバー): データの整合性や永続化ロジックという「状態の管理」に集中。
  • React (UIクライアント): APIから受け取った「状態(State)」を、いかに効率よく宣言的にユーザーへ見せるかに集中。

この経験は、研修講師として「APIを作る本当の意味」と「フロントエンドと疎結合であることの価値」を考える上でも大きな経験になりました。

今後は、認証機能やReactの高度な状態管理、TypeScriptを導入し、より実践的で堅牢なモダンアプリケーション開発に向けて学習を深めていきたいと考えています。

クロノスではモダンな技術を一緒に学んでスキルアップしたい方を募集しています!
興味を持っていただいた方は、ぜひエントリーお願いします!

この記事を書いた人

ラーニング事業本部 事業推進1部 RYOTA

2023年に中途でクロノスに入社
普段はITスキルアカデミーの講師。
技術の習得を通して学習の楽しさを感じていただけるような研修づくりを目指して日々成長中。

おすすめ