Skip to content

Reactとサーバーの通信をAxiosで改善する#

Webアプリケーションではフロントエンドとバックエンドの間で頻繁に通信が行われます。前回は組み込みのfetch APIを使用してサーバーと通信しましたが、今回はAxiosというより強力なHTTPクライアントを使う方法を学びます。

Axiosは、ブラウザとNode.jsで動作するPromiseベースのHTTPクライアントで、fetch APIよりも使いやすく、多くの便利な機能を提供します。

前提#

RustのセットアップAPIの作成データベースアクセス、そしてReactによる画面の作成までを実行しておいてください。

Axiosのインストール#

Reactプロジェクト内でAxiosをインストールしましょう。ターミナルでReactプロジェクトディレクトリに移動し、以下のコマンドを実行します:

cd react-frontend
npm install axios

Axiosをセットアップする#

Axiosのインスタンスを作成し、共通設定を適用することで、より整理されたAPIコードを書くことができます。react-frontend/src/apiディレクトリを作成し、その中にaxios.jsファイルを作成します:

mkdir -p react-frontend/src/api

react-frontend/src/api/axios.jsの内容:

import axios from 'axios';

// Axiosのインスタンスを作成
const api = axios.create({
  baseURL: process.env.NODE_ENV === 'production' 
    ? '/api' // 本番環境用
    : 'http://localhost:8080', // 開発環境用
  headers: {
    'Content-Type': 'application/json',
  },
  // リクエストのタイムアウト時間を設定(ミリ秒)
  timeout: 10000,
});

// リクエストインターセプター
api.interceptors.request.use(
  config => {
    // リクエスト前に処理を追加できます
    // 例:認証トークンの追加など
    return config;
  },
  error => {
    return Promise.reject(error);
  }
);

// レスポンスインターセプター
api.interceptors.response.use(
  response => {
    // レスポンスデータを加工できます
    return response;
  },
  error => {
    // エラーハンドリング
    if (error.response) {
      // サーバーからのエラーレスポンス
      console.error('サーバーエラー:', error.response.data);
    } else if (error.request) {
      // リクエストは送信されたが、レスポンスがない
      console.error('レスポンスがありません:', error.request);
    } else {
      // リクエスト設定中にエラーが発生
      console.error('エラー:', error.message);
    }
    return Promise.reject(error);
  }
);

export default api;

ユーザーAPI機能の作成#

次に、ユーザー操作のためのAPI関数を集めたファイルを作成します。react-frontend/src/api/users.jsを作成します:

import api from './axios';

// ユーザーリストを取得
export const fetchUsers = async () => {
  try {
    const response = await api.get('/api/users');
    return response.data;
  } catch (error) {
    console.error('ユーザーの取得に失敗しました:', error);
    throw error;
  }
};

// 特定のユーザーを取得
export const fetchUserById = async (id) => {
  try {
    const response = await api.get(`/api/users/${id}`);
    return response.data;
  } catch (error) {
    console.error(`ユーザーID${id}の取得に失敗しました:`, error);
    throw error;
  }
};

// 新規ユーザーを作成
export const createUser = async (userData) => {
  try {
    // GETリクエストとしてパラメータを送信(実際のアプリケーションではPOSTを使用することが推奨されます)
    const params = new URLSearchParams(userData).toString();
    const response = await api.get(`/api/users/create?${params}`);
    return response.data;
  } catch (error) {
    console.error('ユーザー作成に失敗しました:', error);
    throw error;
  }
};

// ユーザー情報を更新
export const updateUser = async (id, userData) => {
  try {
    const params = new URLSearchParams(userData).toString();
    const response = await api.get(`/api/users/${id}/update?${params}`);
    return response.data;
  } catch (error) {
    console.error(`ユーザーID${id}の更新に失敗しました:`, error);
    throw error;
  }
};

// ユーザーを削除
export const deleteUser = async (id) => {
  try {
    const response = await api.get(`/api/users/${id}/delete`);
    return response.data;
  } catch (error) {
    console.error(`ユーザーID${id}の削除に失敗しました:`, error);
    throw error;
  }
};

このファイルでは、APIアクセスに関する関数を集約しています。これにより、コンポーネントからAPIの呼び出し方法を分離でき、コードの保守性が向上します。

UserListコンポーネントをAxiosに切り替える#

react-frontend/src/components/UserList.jsを更新して、fetchからAxiosに切り替えます:

import React, { useState, useEffect } from 'react';
import { fetchUsers, deleteUser } from '../api/users';

function UserList({ onUserDeleted }) {
  const [users, setUsers] = useState([]);
  const [loading, setLoading] = useState(true);
  const [error, setError] = useState(null);

  // ユーザーリストを取得
  const loadUsers = async () => {
    try {
      setLoading(true);
      const data = await fetchUsers();
      setUsers(data);
      setError(null);
    } catch (err) {
      setError('ユーザーデータの取得に失敗しました');
    } finally {
      setLoading(false);
    }
  };

  // ユーザーを削除
  const handleDeleteUser = async (id) => {
    if (window.confirm('このユーザーを削除してもよろしいですか?')) {
      try {
        await deleteUser(id);
        // リストから削除したユーザーを除外
        setUsers(users.filter(user => user.id !== id));
        if (onUserDeleted) {
          onUserDeleted();
        }
      } catch (err) {
        alert(`ユーザーの削除に失敗しました: ${err.message}`);
      }
    }
  };

  // コンポーネントのマウント時とキー変更時にユーザーを読み込む
  useEffect(() => {
    loadUsers();
  }, []);

  if (loading) return <div>読み込み中...</div>;
  if (error) return <div>エラー: {error}</div>;

  return (
    <div className="user-list">
      <h2>ユーザー一覧</h2>
      <button onClick={loadUsers} className="refresh-button">更新</button>
      {users.length === 0 ? (
        <p>ユーザーがいません</p>
      ) : (
        <ul>
          {users.map(user => (
            <li key={user.id}>
              {user.id}: {user.account_name} - {user.name} - {user.tel}
              <button 
                onClick={() => handleDeleteUser(user.id)}
                className="delete-button"
              >
                削除
              </button>
            </li>
          ))}
        </ul>
      )}
    </div>
  );
}

export default UserList;

UserInputコンポーネントをAxiosに切り替える#

react-frontend/src/components/UserInput.jsを更新してAxiosを使用します:

import React, { useState } from 'react';
import { createUser } from '../api/users';

function UserInput({ onUserCreated }) {
  const [user, setUser] = useState({
    name: '',
    account_name: '',
    tel: ''
  });
  const [submitting, setSubmitting] = useState(false);

  const handleChange = (e) => {
    const { name, value } = e.target;
    setUser(prevUser => ({
      ...prevUser,
      [name]: value
    }));
  };

  const isFormValid = () => {
    return user.name && user.account_name && user.tel;
  };

  const handleCreateUser = async (e) => {
    e.preventDefault();

    if (!isFormValid()) return;

    try {
      setSubmitting(true);
      const newUser = await createUser(user);

      // 親コンポーネントに通知
      if (onUserCreated) {
        onUserCreated(newUser);
      }

      // フォームをリセット
      setUser({
        name: '',
        account_name: '',
        tel: ''
      });

      alert('ユーザーが正常に作成されました!');
    } catch (error) {
      console.error('ユーザー作成エラー:', error);
      alert(`ユーザー作成エラー: ${error.message}`);
    } finally {
      setSubmitting(false);
    }
  };

  return (
    <div className="user-input">
      <h2>新規ユーザー作成</h2>
      <form onSubmit={handleCreateUser}>
        <div>
          <input
            type="text"
            name="name"
            placeholder="名前"
            value={user.name}
            onChange={handleChange}
            required
          />
        </div>
        <div>
          <input
            type="text"
            name="account_name"
            placeholder="アカウント名"
            value={user.account_name}
            onChange={handleChange}
            required
          />
        </div>
        <div>
          <input
            type="text"
            name="tel"
            placeholder="電話番号"
            value={user.tel}
            onChange={handleChange}
            required
          />
        </div>
        <button 
          type="submit"
          disabled={!isFormValid() || submitting}
        >
          {submitting ? '送信中...' : '新規作成'}
        </button>
      </form>
    </div>
  );
}

export default UserInput;

スタイルの追加#

react-frontend/src/App.cssに追加のスタイルを追加します:

/* 既存のCSS */

.refresh-button {
  margin-left: 10px;
  padding: 5px 10px;
  background-color: #4CAF50;
  color: white;
  border: none;
  border-radius: 4px;
  cursor: pointer;
}

.delete-button {
  margin-left: 10px;
  padding: 3px 8px;
  background-color: #f44336;
  color: white;
  border: none;
  border-radius: 4px;
  cursor: pointer;
}

.delete-button:hover {
  background-color: #d32f2f;
}

form {
  display: flex;
  flex-direction: column;
  align-items: center;
}

メインAppコンポーネントの更新#

react-frontend/src/App.jsを更新して、コンポーネント間の連携を改善します:

import React, { useState } from 'react';
import './App.css';
import UserList from './components/UserList';
import UserInput from './components/UserInput';

function App() {
  const [refreshTrigger, setRefreshTrigger] = useState(0);

  // ユーザーが作成または削除されたらリストを更新
  const handleUserChange = () => {
    setRefreshTrigger(prev => prev + 1);
  };

  return (
    <div className="App">
      <header className="App-header">
        <h1>ユーザー管理アプリケーションAxios版</h1>
      </header>
      <main className="App-content">
        <UserInput onUserCreated={handleUserChange} />
        <UserList 
          key={refreshTrigger}
          onUserDeleted={handleUserChange} 
        />
      </main>
    </div>
  );
}

export default App;

アプリケーションを動かす#

これで、Axiosを使用したRust + Reactアプリケーションが完成しました!両方のサーバーを起動しましょう:

  1. Rustバックエンド: bash cd rust-web-server cargo run

  2. Reactフロントエンド: bash cd react-frontend npm start

ブラウザでhttp://localhost:3000にアクセスすると、ユーザー管理アプリケーションが表示されます。Axiosを使用することで、より整理されたAPIアクセスコードを実現し、エラー処理やリクエスト・レスポンスの変換などを一元化できました。

Axiosのその他の利点:

  1. 自動的な変換:リクエストとレスポンスのデータを自動的にJSON変換します。
  2. インターセプター:リクエストやレスポンスを送信/受信する前に加工できます。
  3. リクエストのキャンセル:進行中のHTTPリクエストをキャンセルできます。
  4. タイムアウト設定:リクエストのタイムアウト時間を設定できます。
  5. CSRF保護:クロスサイトリクエストフォージェリから保護します。
  6. エラーハンドリング:より詳細なエラー情報とハンドリングができます。

おめでとうございます!Rust + ReactによるWebアプリケーション開発の基本を学びました。これらの知識をベースに、さらに高度なアプリケーションを構築していくことができます。